Skip to content

Commit 7163fc1

Browse files
committed
Add field level security management functionality and update documentation
- Introduced salesforce_manage_field_permissions tool for managing field level security, including granting, revoking, and viewing permissions for specific profiles. - Updated README.md to include new features and examples related to field permissions management. - Enhanced manageField tool to automatically grant field access to specified profiles or default to System Administrator. - Added helper functions for handling field permissions in manageField and manageFieldPermissions modules.
1 parent a936469 commit 7163fc1

File tree

4 files changed

+439
-4
lines changed

4 files changed

+439
-4
lines changed

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,18 @@ Manage object fields:
7474
* Add new custom fields
7575
* Modify field properties
7676
* Create relationships
77+
* Automatically grants Field Level Security to System Administrator by default
78+
* Use `grantAccessTo` parameter to specify different profiles
7779
* Example: "Add a Rating picklist field to Account"
7880

81+
### salesforce_manage_field_permissions
82+
Manage Field Level Security (Field Permissions):
83+
* Grant or revoke read/edit access to fields for specific profiles
84+
* View current field permissions
85+
* Bulk update permissions for multiple profiles
86+
* Useful for managing permissions after field creation or for existing fields
87+
* Example: "Grant System Administrator access to Custom_Field__c on Account"
88+
7989
### salesforce_search_all
8090
Search across multiple objects:
8191
* SOSL-based search
@@ -225,6 +235,22 @@ Add to your `claude_desktop_config.json`:
225235
"Add a Rating field to the Feedback object"
226236
"Update sharing settings for the Service Request object"
227237
```
238+
Examples with Field Level Security:
239+
```
240+
# Default - grants access to System Administrator automatically
241+
"Create a Status picklist field on Custom_Object__c"
242+
243+
# Custom profiles - grants access to specified profiles
244+
"Create a Revenue currency field on Account and grant access to Sales User and Marketing User profiles"
245+
```
246+
247+
### Managing Field Permissions
248+
```
249+
"Grant System Administrator access to Custom_Field__c on Account"
250+
"Give read-only access to Rating__c field for Sales User profile"
251+
"View which profiles have access to the Custom_Field__c"
252+
"Revoke field access for specific profiles"
253+
```
228254

229255
### Searching Across Objects
230256
```

src/index.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { AGGREGATE_QUERY, handleAggregateQuery, AggregateQueryArgs } from "./too
1616
import { DML_RECORDS, handleDMLRecords, DMLArgs } from "./tools/dml.js";
1717
import { MANAGE_OBJECT, handleManageObject, ManageObjectArgs } from "./tools/manageObject.js";
1818
import { MANAGE_FIELD, handleManageField, ManageFieldArgs } from "./tools/manageField.js";
19+
import { MANAGE_FIELD_PERMISSIONS, handleManageFieldPermissions, ManageFieldPermissionsArgs } from "./tools/manageFieldPermissions.js";
1920
import { SEARCH_ALL, handleSearchAll, SearchAllArgs, WithClause } from "./tools/searchAll.js";
2021
import { READ_APEX, handleReadApex, ReadApexArgs } from "./tools/readApex.js";
2122
import { WRITE_APEX, handleWriteApex, WriteApexArgs } from "./tools/writeApex.js";
@@ -48,6 +49,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
4849
DML_RECORDS,
4950
MANAGE_OBJECT,
5051
MANAGE_FIELD,
52+
MANAGE_FIELD_PERMISSIONS,
5153
SEARCH_ALL,
5254
READ_APEX,
5355
WRITE_APEX,
@@ -167,11 +169,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
167169
relationshipName: fieldArgs.relationshipName as string | undefined,
168170
deleteConstraint: fieldArgs.deleteConstraint as 'Cascade' | 'Restrict' | 'SetNull' | undefined,
169171
picklistValues: fieldArgs.picklistValues as Array<{ label: string; isDefault?: boolean }> | undefined,
170-
description: fieldArgs.description as string | undefined
172+
description: fieldArgs.description as string | undefined,
173+
grantAccessTo: fieldArgs.grantAccessTo as string[] | undefined
171174
};
172175
return await handleManageField(conn, validatedArgs);
173176
}
174177

178+
case "salesforce_manage_field_permissions": {
179+
const permArgs = args as Record<string, unknown>;
180+
if (!permArgs.operation || !permArgs.objectName || !permArgs.fieldName) {
181+
throw new Error('operation, objectName, and fieldName are required for field permissions management');
182+
}
183+
const validatedArgs: ManageFieldPermissionsArgs = {
184+
operation: permArgs.operation as 'grant' | 'revoke' | 'view',
185+
objectName: permArgs.objectName as string,
186+
fieldName: permArgs.fieldName as string,
187+
profileNames: permArgs.profileNames as string[] | undefined,
188+
readable: permArgs.readable as boolean | undefined,
189+
editable: permArgs.editable as boolean | undefined
190+
};
191+
return await handleManageFieldPermissions(conn, validatedArgs);
192+
}
193+
175194
case "salesforce_search_all": {
176195
const searchArgs = args as Record<string, unknown>;
177196
if (!searchArgs.searchTerm || !Array.isArray(searchArgs.objects)) {

src/tools/manageField.ts

Lines changed: 115 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ export const MANAGE_FIELD: Tool = {
77
- Field Types: Text, Number, Date, Lookup, Master-Detail, Picklist etc.
88
- Properties: Required, Unique, External ID, Length, Scale etc.
99
- Relationships: Create lookups and master-detail relationships
10+
- Automatically grants Field Level Security to System Administrator (or specified profiles)
1011
Examples: Add Rating__c picklist to Account, Create Account lookup on Custom Object
11-
Note: Changes affect metadata and require proper permissions`,
12+
Note: Use grantAccessTo parameter to specify profiles, defaults to System Administrator`,
1213
inputSchema: {
1314
type: "object",
1415
properties: {
@@ -105,6 +106,12 @@ export const MANAGE_FIELD: Tool = {
105106
type: "string",
106107
description: "Description of the field",
107108
optional: true
109+
},
110+
grantAccessTo: {
111+
type: "array",
112+
items: { type: "string" },
113+
description: "Profile names to grant field access to (defaults to ['System Administrator'])",
114+
optional: true
108115
}
109116
},
110117
required: ["operation", "objectName", "fieldName"]
@@ -129,10 +136,104 @@ export interface ManageFieldArgs {
129136
deleteConstraint?: 'Cascade' | 'Restrict' | 'SetNull';
130137
picklistValues?: Array<{ label: string; isDefault?: boolean }>;
131138
description?: string;
139+
grantAccessTo?: string[];
140+
}
141+
142+
// Helper function to set field permissions (simplified version of the one in manageFieldPermissions.ts)
143+
async function grantFieldPermissions(conn: any, objectName: string, fieldName: string, profileNames: string[]): Promise<{success: boolean; message: string}> {
144+
try {
145+
const fieldApiName = fieldName.endsWith('__c') || fieldName.includes('.') ? fieldName : `${fieldName}__c`;
146+
const fullFieldName = `${objectName}.${fieldApiName}`;
147+
148+
// Get profile IDs
149+
const profileQuery = await conn.query(`
150+
SELECT Id, Name
151+
FROM Profile
152+
WHERE Name IN (${profileNames.map(name => `'${name}'`).join(', ')})
153+
`);
154+
155+
if (profileQuery.records.length === 0) {
156+
return { success: false, message: `No profiles found matching: ${profileNames.join(', ')}` };
157+
}
158+
159+
const results: any[] = [];
160+
const errors: string[] = [];
161+
162+
for (const profile of profileQuery.records) {
163+
try {
164+
// Check if permission already exists
165+
const existingPerm = await conn.query(`
166+
SELECT Id, PermissionsRead, PermissionsEdit
167+
FROM FieldPermissions
168+
WHERE ParentId IN (
169+
SELECT Id FROM PermissionSet
170+
WHERE IsOwnedByProfile = true
171+
AND ProfileId = '${profile.Id}'
172+
)
173+
AND Field = '${fullFieldName}'
174+
AND SobjectType = '${objectName}'
175+
LIMIT 1
176+
`);
177+
178+
if (existingPerm.records.length > 0) {
179+
// Update existing permission
180+
await conn.sobject('FieldPermissions').update({
181+
Id: existingPerm.records[0].Id,
182+
PermissionsRead: true,
183+
PermissionsEdit: true
184+
});
185+
results.push(profile.Name);
186+
} else {
187+
// Get the PermissionSet ID for this profile
188+
const permSetQuery = await conn.query(`
189+
SELECT Id FROM PermissionSet
190+
WHERE IsOwnedByProfile = true
191+
AND ProfileId = '${profile.Id}'
192+
LIMIT 1
193+
`);
194+
195+
if (permSetQuery.records.length > 0) {
196+
// Create new permission
197+
await conn.sobject('FieldPermissions').create({
198+
ParentId: permSetQuery.records[0].Id,
199+
SobjectType: objectName,
200+
Field: fullFieldName,
201+
PermissionsRead: true,
202+
PermissionsEdit: true
203+
});
204+
results.push(profile.Name);
205+
} else {
206+
errors.push(profile.Name);
207+
}
208+
}
209+
} catch (error) {
210+
errors.push(profile.Name);
211+
console.error(`Error granting permission to ${profile.Name}:`, error);
212+
}
213+
}
214+
215+
if (results.length > 0) {
216+
return {
217+
success: true,
218+
message: `Field Level Security granted to: ${results.join(', ')}${errors.length > 0 ? `. Failed for: ${errors.join(', ')}` : ''}`
219+
};
220+
} else {
221+
return {
222+
success: false,
223+
message: `Could not grant Field Level Security to any profiles.`
224+
};
225+
}
226+
} catch (error) {
227+
console.error('Error granting field permissions:', error);
228+
return {
229+
success: false,
230+
message: `Field Level Security configuration failed.`
231+
};
232+
}
132233
}
133234

134235
export async function handleManageField(conn: any, args: ManageFieldArgs) {
135-
const { operation, objectName, fieldName, type, ...fieldProps } = args;
236+
const { operation, objectName, fieldName, type, grantAccessTo, ...fieldProps } = args;
136237

137238
try {
138239
if (operation === 'create') {
@@ -205,10 +306,21 @@ export async function handleManageField(conn: any, args: ManageFieldArgs) {
205306
const result = await conn.metadata.create('CustomField', metadata);
206307

207308
if (result && (Array.isArray(result) ? result[0].success : result.success)) {
309+
let permissionMessage = '';
310+
311+
// Grant Field Level Security (default to System Administrator if not specified)
312+
const profilesToGrant = grantAccessTo && grantAccessTo.length > 0 ? grantAccessTo : ['System Administrator'];
313+
314+
// Wait a moment for field to be fully created
315+
await new Promise(resolve => setTimeout(resolve, 2000));
316+
317+
const permissionResult = await grantFieldPermissions(conn, objectName, fieldName, profilesToGrant);
318+
permissionMessage = `\n${permissionResult.message}`;
319+
208320
return {
209321
content: [{
210322
type: "text",
211-
text: `Successfully created custom field ${fieldName}__c on ${objectName}`
323+
text: `Successfully created custom field ${fieldName}__c on ${objectName}.${permissionMessage}`
212324
}],
213325
isError: false,
214326
};

0 commit comments

Comments
 (0)