@rohithvemulapally/mcp-server-salesforce 0.0.6
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 +357 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +312 -0
- package/dist/tools/aggregateQuery.d.ts +18 -0
- package/dist/tools/aggregateQuery.js +250 -0
- package/dist/tools/describe.d.ts +9 -0
- package/dist/tools/describe.js +33 -0
- package/dist/tools/dml.d.ts +15 -0
- package/dist/tools/dml.js +105 -0
- package/dist/tools/executeAnonymous.d.ts +25 -0
- package/dist/tools/executeAnonymous.js +130 -0
- package/dist/tools/manageDebugLogs.d.ts +30 -0
- package/dist/tools/manageDebugLogs.js +424 -0
- package/dist/tools/manageField.d.ts +32 -0
- package/dist/tools/manageField.js +349 -0
- package/dist/tools/manageFieldPermissions.d.ts +17 -0
- package/dist/tools/manageFieldPermissions.js +247 -0
- package/dist/tools/manageObject.d.ts +20 -0
- package/dist/tools/manageObject.js +138 -0
- package/dist/tools/metadata.d.ts +9 -0
- package/dist/tools/metadata.js +66 -0
- package/dist/tools/query.d.ts +16 -0
- package/dist/tools/query.js +114 -0
- package/dist/tools/readApex.d.ts +26 -0
- package/dist/tools/readApex.js +165 -0
- package/dist/tools/readApexTrigger.d.ts +26 -0
- package/dist/tools/readApexTrigger.js +165 -0
- package/dist/tools/search.d.ts +9 -0
- package/dist/tools/search.js +45 -0
- package/dist/tools/searchAll.d.ts +29 -0
- package/dist/tools/searchAll.js +250 -0
- package/dist/tools/writeApex.d.ts +27 -0
- package/dist/tools/writeApex.js +154 -0
- package/dist/tools/writeApexTrigger.d.ts +28 -0
- package/dist/tools/writeApexTrigger.js +178 -0
- package/dist/types/connection.d.ts +52 -0
- package/dist/types/connection.js +21 -0
- package/dist/types/metadata.d.ts +43 -0
- package/dist/types/metadata.js +1 -0
- package/dist/types/salesforce.d.ts +33 -0
- package/dist/types/salesforce.js +1 -0
- package/dist/utils/connection.d.ts +7 -0
- package/dist/utils/connection.js +172 -0
- package/dist/utils/errorHandler.d.ts +15 -0
- package/dist/utils/errorHandler.js +23 -0
- package/package.json +39 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
export const MANAGE_FIELD = {
|
|
2
|
+
name: "salesforce_manage_field",
|
|
3
|
+
description: `Create new custom fields or modify existing fields on any Salesforce object:
|
|
4
|
+
- Field Types: Text, Number, Date, Lookup, Master-Detail, Picklist etc.
|
|
5
|
+
- Properties: Required, Unique, External ID, Length, Scale etc.
|
|
6
|
+
- Relationships: Create lookups and master-detail relationships
|
|
7
|
+
- Automatically grants Field Level Security to System Administrator (or specified profiles)
|
|
8
|
+
Examples: Add Rating__c picklist to Account, Create Account lookup on Custom Object
|
|
9
|
+
Note: Use grantAccessTo parameter to specify profiles, defaults to System Administrator`,
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
operation: {
|
|
14
|
+
type: "string",
|
|
15
|
+
enum: ["create", "update"],
|
|
16
|
+
description: "Whether to create new field or update existing"
|
|
17
|
+
},
|
|
18
|
+
objectName: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "API name of the object to add/modify the field"
|
|
21
|
+
},
|
|
22
|
+
fieldName: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "API name for the field (without __c suffix)"
|
|
25
|
+
},
|
|
26
|
+
label: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "Label for the field",
|
|
29
|
+
optional: true
|
|
30
|
+
},
|
|
31
|
+
type: {
|
|
32
|
+
type: "string",
|
|
33
|
+
enum: ["Checkbox", "Currency", "Date", "DateTime", "Email", "Number", "Percent",
|
|
34
|
+
"Phone", "Picklist", "MultiselectPicklist", "Text", "TextArea", "LongTextArea",
|
|
35
|
+
"Html", "Url", "Lookup", "MasterDetail"],
|
|
36
|
+
description: "Field type (required for create)",
|
|
37
|
+
optional: true
|
|
38
|
+
},
|
|
39
|
+
required: {
|
|
40
|
+
type: "boolean",
|
|
41
|
+
description: "Whether the field is required",
|
|
42
|
+
optional: true
|
|
43
|
+
},
|
|
44
|
+
unique: {
|
|
45
|
+
type: "boolean",
|
|
46
|
+
description: "Whether the field value must be unique",
|
|
47
|
+
optional: true
|
|
48
|
+
},
|
|
49
|
+
externalId: {
|
|
50
|
+
type: "boolean",
|
|
51
|
+
description: "Whether the field is an external ID",
|
|
52
|
+
optional: true
|
|
53
|
+
},
|
|
54
|
+
length: {
|
|
55
|
+
type: "number",
|
|
56
|
+
description: "Length for text fields",
|
|
57
|
+
optional: true
|
|
58
|
+
},
|
|
59
|
+
precision: {
|
|
60
|
+
type: "number",
|
|
61
|
+
description: "Precision for numeric fields",
|
|
62
|
+
optional: true
|
|
63
|
+
},
|
|
64
|
+
scale: {
|
|
65
|
+
type: "number",
|
|
66
|
+
description: "Scale for numeric fields",
|
|
67
|
+
optional: true
|
|
68
|
+
},
|
|
69
|
+
referenceTo: {
|
|
70
|
+
type: "string",
|
|
71
|
+
description: "API name of the object to reference (for Lookup/MasterDetail)",
|
|
72
|
+
optional: true
|
|
73
|
+
},
|
|
74
|
+
relationshipLabel: {
|
|
75
|
+
type: "string",
|
|
76
|
+
description: "Label for the relationship (for Lookup/MasterDetail)",
|
|
77
|
+
optional: true
|
|
78
|
+
},
|
|
79
|
+
relationshipName: {
|
|
80
|
+
type: "string",
|
|
81
|
+
description: "API name for the relationship (for Lookup/MasterDetail)",
|
|
82
|
+
optional: true
|
|
83
|
+
},
|
|
84
|
+
deleteConstraint: {
|
|
85
|
+
type: "string",
|
|
86
|
+
enum: ["Cascade", "Restrict", "SetNull"],
|
|
87
|
+
description: "Delete constraint for Lookup fields",
|
|
88
|
+
optional: true
|
|
89
|
+
},
|
|
90
|
+
picklistValues: {
|
|
91
|
+
type: "array",
|
|
92
|
+
items: {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: {
|
|
95
|
+
label: { type: "string" },
|
|
96
|
+
isDefault: { type: "boolean", optional: true }
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
description: "Values for Picklist/MultiselectPicklist fields",
|
|
100
|
+
optional: true
|
|
101
|
+
},
|
|
102
|
+
description: {
|
|
103
|
+
type: "string",
|
|
104
|
+
description: "Description of the field",
|
|
105
|
+
optional: true
|
|
106
|
+
},
|
|
107
|
+
grantAccessTo: {
|
|
108
|
+
type: "array",
|
|
109
|
+
items: { type: "string" },
|
|
110
|
+
description: "Profile names to grant field access to (defaults to ['System Administrator'])",
|
|
111
|
+
optional: true
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
required: ["operation", "objectName", "fieldName"]
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
// Helper function to set field permissions (simplified version of the one in manageFieldPermissions.ts)
|
|
118
|
+
async function grantFieldPermissions(conn, objectName, fieldName, profileNames) {
|
|
119
|
+
try {
|
|
120
|
+
const fieldApiName = fieldName.endsWith('__c') || fieldName.includes('.') ? fieldName : `${fieldName}__c`;
|
|
121
|
+
const fullFieldName = `${objectName}.${fieldApiName}`;
|
|
122
|
+
// Get profile IDs
|
|
123
|
+
const profileQuery = await conn.query(`
|
|
124
|
+
SELECT Id, Name
|
|
125
|
+
FROM Profile
|
|
126
|
+
WHERE Name IN (${profileNames.map(name => `'${name}'`).join(', ')})
|
|
127
|
+
`);
|
|
128
|
+
if (profileQuery.records.length === 0) {
|
|
129
|
+
return { success: false, message: `No profiles found matching: ${profileNames.join(', ')}` };
|
|
130
|
+
}
|
|
131
|
+
const results = [];
|
|
132
|
+
const errors = [];
|
|
133
|
+
for (const profile of profileQuery.records) {
|
|
134
|
+
try {
|
|
135
|
+
// Check if permission already exists
|
|
136
|
+
const existingPerm = await conn.query(`
|
|
137
|
+
SELECT Id, PermissionsRead, PermissionsEdit
|
|
138
|
+
FROM FieldPermissions
|
|
139
|
+
WHERE ParentId IN (
|
|
140
|
+
SELECT Id FROM PermissionSet
|
|
141
|
+
WHERE IsOwnedByProfile = true
|
|
142
|
+
AND ProfileId = '${profile.Id}'
|
|
143
|
+
)
|
|
144
|
+
AND Field = '${fullFieldName}'
|
|
145
|
+
AND SobjectType = '${objectName}'
|
|
146
|
+
LIMIT 1
|
|
147
|
+
`);
|
|
148
|
+
if (existingPerm.records.length > 0) {
|
|
149
|
+
// Update existing permission
|
|
150
|
+
await conn.sobject('FieldPermissions').update({
|
|
151
|
+
Id: existingPerm.records[0].Id,
|
|
152
|
+
PermissionsRead: true,
|
|
153
|
+
PermissionsEdit: true
|
|
154
|
+
});
|
|
155
|
+
results.push(profile.Name);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
// Get the PermissionSet ID for this profile
|
|
159
|
+
const permSetQuery = await conn.query(`
|
|
160
|
+
SELECT Id FROM PermissionSet
|
|
161
|
+
WHERE IsOwnedByProfile = true
|
|
162
|
+
AND ProfileId = '${profile.Id}'
|
|
163
|
+
LIMIT 1
|
|
164
|
+
`);
|
|
165
|
+
if (permSetQuery.records.length > 0) {
|
|
166
|
+
// Create new permission
|
|
167
|
+
await conn.sobject('FieldPermissions').create({
|
|
168
|
+
ParentId: permSetQuery.records[0].Id,
|
|
169
|
+
SobjectType: objectName,
|
|
170
|
+
Field: fullFieldName,
|
|
171
|
+
PermissionsRead: true,
|
|
172
|
+
PermissionsEdit: true
|
|
173
|
+
});
|
|
174
|
+
results.push(profile.Name);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
errors.push(profile.Name);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
errors.push(profile.Name);
|
|
183
|
+
console.error(`Error granting permission to ${profile.Name}:`, error);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (results.length > 0) {
|
|
187
|
+
return {
|
|
188
|
+
success: true,
|
|
189
|
+
message: `Field Level Security granted to: ${results.join(', ')}${errors.length > 0 ? `. Failed for: ${errors.join(', ')}` : ''}`
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
return {
|
|
194
|
+
success: false,
|
|
195
|
+
message: `Could not grant Field Level Security to any profiles.`
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
console.error('Error granting field permissions:', error);
|
|
201
|
+
return {
|
|
202
|
+
success: false,
|
|
203
|
+
message: `Field Level Security configuration failed.`
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
export async function handleManageField(conn, args) {
|
|
208
|
+
const { operation, objectName, fieldName, type, grantAccessTo, ...fieldProps } = args;
|
|
209
|
+
try {
|
|
210
|
+
if (operation === 'create') {
|
|
211
|
+
if (!type) {
|
|
212
|
+
throw new Error('Field type is required for field creation');
|
|
213
|
+
}
|
|
214
|
+
// Prepare base metadata for the new field
|
|
215
|
+
const metadata = {
|
|
216
|
+
fullName: `${objectName}.${fieldName}__c`,
|
|
217
|
+
label: fieldProps.label || fieldName,
|
|
218
|
+
type,
|
|
219
|
+
...(fieldProps.required && { required: fieldProps.required }),
|
|
220
|
+
...(fieldProps.unique && { unique: fieldProps.unique }),
|
|
221
|
+
...(fieldProps.externalId && { externalId: fieldProps.externalId }),
|
|
222
|
+
...(fieldProps.description && { description: fieldProps.description })
|
|
223
|
+
};
|
|
224
|
+
// Add type-specific properties
|
|
225
|
+
switch (type) {
|
|
226
|
+
case 'MasterDetail':
|
|
227
|
+
case 'Lookup':
|
|
228
|
+
if (fieldProps.referenceTo) {
|
|
229
|
+
metadata.referenceTo = fieldProps.referenceTo;
|
|
230
|
+
metadata.relationshipName = fieldProps.relationshipName;
|
|
231
|
+
metadata.relationshipLabel = fieldProps.relationshipLabel || fieldProps.relationshipName;
|
|
232
|
+
if (type === 'Lookup' && fieldProps.deleteConstraint) {
|
|
233
|
+
metadata.deleteConstraint = fieldProps.deleteConstraint;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
case 'TextArea':
|
|
238
|
+
metadata.type = 'LongTextArea';
|
|
239
|
+
metadata.length = fieldProps.length || 32768;
|
|
240
|
+
metadata.visibleLines = 3;
|
|
241
|
+
break;
|
|
242
|
+
case 'Text':
|
|
243
|
+
if (fieldProps.length) {
|
|
244
|
+
metadata.length = fieldProps.length;
|
|
245
|
+
}
|
|
246
|
+
break;
|
|
247
|
+
case 'Number':
|
|
248
|
+
if (fieldProps.precision) {
|
|
249
|
+
metadata.precision = fieldProps.precision;
|
|
250
|
+
metadata.scale = fieldProps.scale || 0;
|
|
251
|
+
}
|
|
252
|
+
break;
|
|
253
|
+
case 'Picklist':
|
|
254
|
+
case 'MultiselectPicklist':
|
|
255
|
+
if (fieldProps.picklistValues) {
|
|
256
|
+
metadata.valueSet = {
|
|
257
|
+
valueSetDefinition: {
|
|
258
|
+
sorted: true,
|
|
259
|
+
value: fieldProps.picklistValues.map(val => ({
|
|
260
|
+
fullName: val.label,
|
|
261
|
+
default: val.isDefault || false,
|
|
262
|
+
label: val.label
|
|
263
|
+
}))
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
// Create the field
|
|
270
|
+
const result = await conn.metadata.create('CustomField', metadata);
|
|
271
|
+
if (result && (Array.isArray(result) ? result[0].success : result.success)) {
|
|
272
|
+
let permissionMessage = '';
|
|
273
|
+
// Grant Field Level Security (default to System Administrator if not specified)
|
|
274
|
+
const profilesToGrant = grantAccessTo && grantAccessTo.length > 0 ? grantAccessTo : ['System Administrator'];
|
|
275
|
+
// Wait a moment for field to be fully created
|
|
276
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
277
|
+
const permissionResult = await grantFieldPermissions(conn, objectName, fieldName, profilesToGrant);
|
|
278
|
+
permissionMessage = `\n${permissionResult.message}`;
|
|
279
|
+
return {
|
|
280
|
+
content: [{
|
|
281
|
+
type: "text",
|
|
282
|
+
text: `Successfully created custom field ${fieldName}__c on ${objectName}.${permissionMessage}`
|
|
283
|
+
}],
|
|
284
|
+
isError: false,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
// For update, first get existing metadata
|
|
290
|
+
const existingMetadata = await conn.metadata.read('CustomField', [`${objectName}.${fieldName}__c`]);
|
|
291
|
+
const currentMetadata = Array.isArray(existingMetadata) ? existingMetadata[0] : existingMetadata;
|
|
292
|
+
if (!currentMetadata) {
|
|
293
|
+
throw new Error(`Field ${fieldName}__c not found on object ${objectName}`);
|
|
294
|
+
}
|
|
295
|
+
// Prepare update metadata
|
|
296
|
+
const metadata = {
|
|
297
|
+
...currentMetadata,
|
|
298
|
+
...(fieldProps.label && { label: fieldProps.label }),
|
|
299
|
+
...(fieldProps.required !== undefined && { required: fieldProps.required }),
|
|
300
|
+
...(fieldProps.unique !== undefined && { unique: fieldProps.unique }),
|
|
301
|
+
...(fieldProps.externalId !== undefined && { externalId: fieldProps.externalId }),
|
|
302
|
+
...(fieldProps.description !== undefined && { description: fieldProps.description }),
|
|
303
|
+
...(fieldProps.length && { length: fieldProps.length }),
|
|
304
|
+
...(fieldProps.precision && { precision: fieldProps.precision, scale: fieldProps.scale || 0 })
|
|
305
|
+
};
|
|
306
|
+
// Special handling for picklist values if provided
|
|
307
|
+
if (fieldProps.picklistValues &&
|
|
308
|
+
(currentMetadata.type === 'Picklist' || currentMetadata.type === 'MultiselectPicklist')) {
|
|
309
|
+
metadata.valueSet = {
|
|
310
|
+
valueSetDefinition: {
|
|
311
|
+
sorted: true,
|
|
312
|
+
value: fieldProps.picklistValues.map(val => ({
|
|
313
|
+
fullName: val.label,
|
|
314
|
+
default: val.isDefault || false,
|
|
315
|
+
label: val.label
|
|
316
|
+
}))
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
// Update the field
|
|
321
|
+
const result = await conn.metadata.update('CustomField', metadata);
|
|
322
|
+
if (result && (Array.isArray(result) ? result[0].success : result.success)) {
|
|
323
|
+
return {
|
|
324
|
+
content: [{
|
|
325
|
+
type: "text",
|
|
326
|
+
text: `Successfully updated custom field ${fieldName}__c on ${objectName}`
|
|
327
|
+
}],
|
|
328
|
+
isError: false,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
content: [{
|
|
334
|
+
type: "text",
|
|
335
|
+
text: `Failed to ${operation} custom field ${fieldName}__c`
|
|
336
|
+
}],
|
|
337
|
+
isError: true,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
return {
|
|
342
|
+
content: [{
|
|
343
|
+
type: "text",
|
|
344
|
+
text: `Error ${operation === 'create' ? 'creating' : 'updating'} custom field: ${error instanceof Error ? error.message : String(error)}`
|
|
345
|
+
}],
|
|
346
|
+
isError: true,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
export declare const MANAGE_FIELD_PERMISSIONS: Tool;
|
|
3
|
+
export interface ManageFieldPermissionsArgs {
|
|
4
|
+
operation: 'grant' | 'revoke' | 'view';
|
|
5
|
+
objectName: string;
|
|
6
|
+
fieldName: string;
|
|
7
|
+
profileNames?: string[];
|
|
8
|
+
readable?: boolean;
|
|
9
|
+
editable?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function handleManageFieldPermissions(conn: any, args: ManageFieldPermissionsArgs): Promise<{
|
|
12
|
+
content: {
|
|
13
|
+
type: string;
|
|
14
|
+
text: string;
|
|
15
|
+
}[];
|
|
16
|
+
isError: boolean;
|
|
17
|
+
}>;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
export const MANAGE_FIELD_PERMISSIONS = {
|
|
2
|
+
name: "salesforce_manage_field_permissions",
|
|
3
|
+
description: `Manage Field Level Security (Field Permissions) for custom and standard fields.
|
|
4
|
+
- Grant or revoke read/edit access to fields for specific profiles or permission sets
|
|
5
|
+
- View current field permissions
|
|
6
|
+
- Bulk update permissions for multiple profiles
|
|
7
|
+
|
|
8
|
+
Examples:
|
|
9
|
+
1. Grant System Administrator access to a field
|
|
10
|
+
2. Give read-only access to a field for specific profiles
|
|
11
|
+
3. Check which profiles have access to a field`,
|
|
12
|
+
inputSchema: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
operation: {
|
|
16
|
+
type: "string",
|
|
17
|
+
enum: ["grant", "revoke", "view"],
|
|
18
|
+
description: "Operation to perform on field permissions"
|
|
19
|
+
},
|
|
20
|
+
objectName: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "API name of the object (e.g., 'Account', 'Custom_Object__c')"
|
|
23
|
+
},
|
|
24
|
+
fieldName: {
|
|
25
|
+
type: "string",
|
|
26
|
+
description: "API name of the field (e.g., 'Custom_Field__c')"
|
|
27
|
+
},
|
|
28
|
+
profileNames: {
|
|
29
|
+
type: "array",
|
|
30
|
+
items: { type: "string" },
|
|
31
|
+
description: "Names of profiles to grant/revoke access (e.g., ['System Administrator', 'Sales User'])",
|
|
32
|
+
optional: true
|
|
33
|
+
},
|
|
34
|
+
readable: {
|
|
35
|
+
type: "boolean",
|
|
36
|
+
description: "Grant/revoke read access (default: true)",
|
|
37
|
+
optional: true
|
|
38
|
+
},
|
|
39
|
+
editable: {
|
|
40
|
+
type: "boolean",
|
|
41
|
+
description: "Grant/revoke edit access (default: true)",
|
|
42
|
+
optional: true
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
required: ["operation", "objectName", "fieldName"]
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
export async function handleManageFieldPermissions(conn, args) {
|
|
49
|
+
const { operation, objectName, fieldName, readable = true, editable = true } = args;
|
|
50
|
+
let { profileNames } = args;
|
|
51
|
+
try {
|
|
52
|
+
// Ensure field name has __c suffix if it's a custom field and doesn't already have it
|
|
53
|
+
const fieldApiName = fieldName.endsWith('__c') || fieldName.includes('.') ? fieldName : `${fieldName}__c`;
|
|
54
|
+
const fullFieldName = `${objectName}.${fieldApiName}`;
|
|
55
|
+
if (operation === 'view') {
|
|
56
|
+
// Query existing field permissions
|
|
57
|
+
const permissionsQuery = `
|
|
58
|
+
SELECT Id, Parent.ProfileId, Parent.Profile.Name, Parent.IsOwnedByProfile,
|
|
59
|
+
Parent.PermissionSetId, Parent.PermissionSet.Name,
|
|
60
|
+
Field, PermissionsRead, PermissionsEdit
|
|
61
|
+
FROM FieldPermissions
|
|
62
|
+
WHERE SobjectType = '${objectName}'
|
|
63
|
+
AND Field = '${fullFieldName}'
|
|
64
|
+
ORDER BY Parent.Profile.Name
|
|
65
|
+
`;
|
|
66
|
+
const result = await conn.query(permissionsQuery);
|
|
67
|
+
if (result.records.length === 0) {
|
|
68
|
+
return {
|
|
69
|
+
content: [{
|
|
70
|
+
type: "text",
|
|
71
|
+
text: `No field permissions found for ${fullFieldName}. This field might not have any specific permissions set, or it might be universally accessible.`
|
|
72
|
+
}],
|
|
73
|
+
isError: false,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
let responseText = `Field permissions for ${fullFieldName}:\n\n`;
|
|
77
|
+
result.records.forEach((perm) => {
|
|
78
|
+
const name = perm.Parent.IsOwnedByProfile
|
|
79
|
+
? perm.Parent.Profile?.Name
|
|
80
|
+
: perm.Parent.PermissionSet?.Name;
|
|
81
|
+
const type = perm.Parent.IsOwnedByProfile ? 'Profile' : 'Permission Set';
|
|
82
|
+
responseText += `${type}: ${name}\n`;
|
|
83
|
+
responseText += ` - Read Access: ${perm.PermissionsRead ? 'Yes' : 'No'}\n`;
|
|
84
|
+
responseText += ` - Edit Access: ${perm.PermissionsEdit ? 'Yes' : 'No'}\n\n`;
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
content: [{
|
|
88
|
+
type: "text",
|
|
89
|
+
text: responseText
|
|
90
|
+
}],
|
|
91
|
+
isError: false,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// For grant/revoke operations
|
|
95
|
+
if (!profileNames || profileNames.length === 0) {
|
|
96
|
+
// If no profiles specified, default to System Administrator
|
|
97
|
+
profileNames = ['System Administrator'];
|
|
98
|
+
}
|
|
99
|
+
// Get profile IDs
|
|
100
|
+
const profileQuery = await conn.query(`
|
|
101
|
+
SELECT Id, Name
|
|
102
|
+
FROM Profile
|
|
103
|
+
WHERE Name IN (${profileNames.map(name => `'${name}'`).join(', ')})
|
|
104
|
+
`);
|
|
105
|
+
if (profileQuery.records.length === 0) {
|
|
106
|
+
return {
|
|
107
|
+
content: [{
|
|
108
|
+
type: "text",
|
|
109
|
+
text: `No profiles found matching: ${profileNames.join(', ')}`
|
|
110
|
+
}],
|
|
111
|
+
isError: true,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const results = [];
|
|
115
|
+
const errors = [];
|
|
116
|
+
for (const profile of profileQuery.records) {
|
|
117
|
+
try {
|
|
118
|
+
if (operation === 'grant') {
|
|
119
|
+
// First, check if permission already exists
|
|
120
|
+
const existingPerm = await conn.query(`
|
|
121
|
+
SELECT Id, PermissionsRead, PermissionsEdit
|
|
122
|
+
FROM FieldPermissions
|
|
123
|
+
WHERE ParentId IN (
|
|
124
|
+
SELECT Id FROM PermissionSet
|
|
125
|
+
WHERE IsOwnedByProfile = true
|
|
126
|
+
AND ProfileId = '${profile.Id}'
|
|
127
|
+
)
|
|
128
|
+
AND Field = '${fullFieldName}'
|
|
129
|
+
AND SobjectType = '${objectName}'
|
|
130
|
+
LIMIT 1
|
|
131
|
+
`);
|
|
132
|
+
if (existingPerm.records.length > 0) {
|
|
133
|
+
// Update existing permission
|
|
134
|
+
const updateResult = await conn.sobject('FieldPermissions').update({
|
|
135
|
+
Id: existingPerm.records[0].Id,
|
|
136
|
+
PermissionsRead: readable,
|
|
137
|
+
PermissionsEdit: editable && readable // Edit requires read
|
|
138
|
+
});
|
|
139
|
+
results.push({
|
|
140
|
+
profile: profile.Name,
|
|
141
|
+
action: 'updated',
|
|
142
|
+
success: updateResult.success
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// Get the PermissionSet ID for this profile
|
|
147
|
+
const permSetQuery = await conn.query(`
|
|
148
|
+
SELECT Id FROM PermissionSet
|
|
149
|
+
WHERE IsOwnedByProfile = true
|
|
150
|
+
AND ProfileId = '${profile.Id}'
|
|
151
|
+
LIMIT 1
|
|
152
|
+
`);
|
|
153
|
+
if (permSetQuery.records.length > 0) {
|
|
154
|
+
// Create new permission
|
|
155
|
+
const createResult = await conn.sobject('FieldPermissions').create({
|
|
156
|
+
ParentId: permSetQuery.records[0].Id,
|
|
157
|
+
SobjectType: objectName,
|
|
158
|
+
Field: fullFieldName,
|
|
159
|
+
PermissionsRead: readable,
|
|
160
|
+
PermissionsEdit: editable && readable // Edit requires read
|
|
161
|
+
});
|
|
162
|
+
results.push({
|
|
163
|
+
profile: profile.Name,
|
|
164
|
+
action: 'created',
|
|
165
|
+
success: createResult.success
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
errors.push(`Could not find permission set for profile: ${profile.Name}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else if (operation === 'revoke') {
|
|
174
|
+
// Find and delete the permission
|
|
175
|
+
const existingPerm = await conn.query(`
|
|
176
|
+
SELECT Id
|
|
177
|
+
FROM FieldPermissions
|
|
178
|
+
WHERE ParentId IN (
|
|
179
|
+
SELECT Id FROM PermissionSet
|
|
180
|
+
WHERE IsOwnedByProfile = true
|
|
181
|
+
AND ProfileId = '${profile.Id}'
|
|
182
|
+
)
|
|
183
|
+
AND Field = '${fullFieldName}'
|
|
184
|
+
AND SobjectType = '${objectName}'
|
|
185
|
+
LIMIT 1
|
|
186
|
+
`);
|
|
187
|
+
if (existingPerm.records.length > 0) {
|
|
188
|
+
const deleteResult = await conn.sobject('FieldPermissions').delete(existingPerm.records[0].Id);
|
|
189
|
+
results.push({
|
|
190
|
+
profile: profile.Name,
|
|
191
|
+
action: 'revoked',
|
|
192
|
+
success: true
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
results.push({
|
|
197
|
+
profile: profile.Name,
|
|
198
|
+
action: 'no permission found',
|
|
199
|
+
success: true
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
errors.push(`${profile.Name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Format response
|
|
209
|
+
let responseText = `Field permission ${operation} operation completed for ${fullFieldName}:\n\n`;
|
|
210
|
+
const successful = results.filter(r => r.success);
|
|
211
|
+
const failed = results.filter(r => !r.success);
|
|
212
|
+
if (successful.length > 0) {
|
|
213
|
+
responseText += 'Successful:\n';
|
|
214
|
+
successful.forEach(r => {
|
|
215
|
+
responseText += ` - ${r.profile}: ${r.action}\n`;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
if (failed.length > 0 || errors.length > 0) {
|
|
219
|
+
responseText += '\nFailed:\n';
|
|
220
|
+
failed.forEach(r => {
|
|
221
|
+
responseText += ` - ${r.profile}: ${r.action}\n`;
|
|
222
|
+
});
|
|
223
|
+
errors.forEach(e => {
|
|
224
|
+
responseText += ` - ${e}\n`;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (operation === 'grant') {
|
|
228
|
+
responseText += `\nPermissions granted:\n - Read: ${readable ? 'Yes' : 'No'}\n - Edit: ${editable ? 'Yes' : 'No'}`;
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
content: [{
|
|
232
|
+
type: "text",
|
|
233
|
+
text: responseText
|
|
234
|
+
}],
|
|
235
|
+
isError: false,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
return {
|
|
240
|
+
content: [{
|
|
241
|
+
type: "text",
|
|
242
|
+
text: `Error managing field permissions: ${error instanceof Error ? error.message : String(error)}`
|
|
243
|
+
}],
|
|
244
|
+
isError: true,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
export declare const MANAGE_OBJECT: Tool;
|
|
3
|
+
export interface ManageObjectArgs {
|
|
4
|
+
operation: 'create' | 'update';
|
|
5
|
+
objectName: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
pluralLabel?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
nameFieldLabel?: string;
|
|
10
|
+
nameFieldType?: 'Text' | 'AutoNumber';
|
|
11
|
+
nameFieldFormat?: string;
|
|
12
|
+
sharingModel?: 'ReadWrite' | 'Read' | 'Private' | 'ControlledByParent';
|
|
13
|
+
}
|
|
14
|
+
export declare function handleManageObject(conn: any, args: ManageObjectArgs): Promise<{
|
|
15
|
+
content: {
|
|
16
|
+
type: string;
|
|
17
|
+
text: string;
|
|
18
|
+
}[];
|
|
19
|
+
isError: boolean;
|
|
20
|
+
}>;
|