SObjectActions
sfka-invocableMCP
제네릭하고 특정 객체에 종속되지 않는 invocable Apex 액션(27개의 MCP 도구)으로, 탐색(describe, picklists, list flows, access check, record summary), 조회(read, find, count, aggregate, search, related), 쓰기(create, upsert, update, clone, delete, undelete, validate dry-run, assign owner, change record type), intent shortcuts(log activity, check, close case, convert lead, post to Chatter, add note, attach file), 그리고 run flow를 포함합니다. 이 액션들은 Salesforce에 호스팅된 MCP 서버(McpServerDefinition = SObjectActions)의 도구로 노출됩니다. 동일한 액션은 Flow, Agentforce 에이전트 액션 및 REST API(/services/data/vXX.X/actions/custom/apex/<ClassName>)에서도 호출할 수 있습니다.
모든 액션은 objectApiName 문자열과 함께 레코드 또는 레코드 Id를 받으므로, 한 번의 배포로 추가 코드 없이 모든 표준/사용자 객체를 지원합니다.
빠른 시작 (반복 가능)
scripts/setup.sh <org-alias> [--no-fixtures] [--no-eca] [--no-tests] [--no-smoke] [--assign user@example.com]테스트 픽스처, 27개 액션 + echo flow + permission set, MCP 서버 정의, External Client App을 배포하고, SObjectActions_User를 할당합니다. 45개 전체 단위 테스트와 45개 항목을 검사하는 REST smoke test를 실행한 뒤, consumer key와 남은 두 가지 수동 단계(Setup에서 서버 활성화, 객체 CRUD/FLS 부여)를 출력합니다. 멱동적이며, Enhanced Notes / record types가 있는 조직과 없는 조직 모두에서 검증했습니다.
대안: unlocked package SObject Actions MCP (Apex + flow + permission set; scripts/package.sh install <org> <04t>는 Salesforce가 패키지에 허용하지 않는 mcp/ 및 eca/도 배포합니다). 또한 scripts/scratch.sh(새로운 scratch org용 전체 설치)와 .github/workflows/validate.yml(PR에 대한 check-only 배포 + 테스트 + 문서 drift 확인, secret SF_AUTH_URL 필요)이 있습니다.
force-app/ (패키지 가능: classes, flows, permission sets), mcp/ (McpServerDefinition), eca/ (External Client App), test-fixtures/ (선택).
Related MCP server: Salesforce-Hosted-Custom-Mcp-Server
Documentation map
Document | What is it for |
| 사용 가이드: 배포, 활성화, 컨벤션, 도구별 참조, 보안, 한도, 오류, 테스트 |
| 생성된 도구 매니페스트: 각 도구의 입력, 출력, annotation ( |
| 동일한 매니페스트를 기계가 읽을 수 있는 JSON으로 (agent 프롬프트, 문서 사이트, CI diff용) |
| 클래스들이 어떻게 연결되는지, 공용 헬퍼, 설계 규칙, 도구 추가하는 방법 |
| 외부 클라이언트 인증 방법 (ECA, PKCE, refresh token, Postman, headless fallback) |
| 생성된 Postman collection: OAuth 사전 설정, 도구별 MCP + REST 요청 |
| 30분 발표용 스크립트, 23-슬라이드 덱을 슬라이드별로 설명 |
| 동일 발표의 15분 버전: 9-/458, 데모 순간 하나 |
| 발표 뒤 참고 자료: 카탈로그 설명, 질문 목록, 언급하면 안 되는 주장 |
| 릴리스 히스토리 |
목차
[convertLead](#convertlead--sobjectconvert leadaction)
Tool families
Family | Tools |
Discover |
|
Read |
|
Write |
|
Intent shortcuts |
|
Automation |
|
각 도구 입력/출력 전체 참조: docs/TOOLS.md.
구성
모든 소는 force-app/main/default/.에 있습니다.
경로 | 용도 |
| 호출 가능한 |
| 호출 가능한 |
| 호출 가능한 |
| 호출 가능한 |
| 호출 가능한 |
| 호출 가능한 |
| 호출 가능한 |
| 호출 가능한 |
| 호출 가능한 |
| 호출 가능한 |
| 호출 가능한 |
| 호출 가능한 |
| 호출 가능한 |
| 호출 가능한 |
| 호출 가능한 |
| 호출 가능한 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| runFlow 테스트 픽스처로 사용되는 아주 작은 자동 실행 플로우 |
| 선택 사항: |
| 공통 헬퍼: 타입 확인, JSON -> SObject, 필드 검증, 레이블 조회, 중복 안전 DML |
| create/read/update/delete에 대한 14개 테스트 |
| upsert/find/describe에 대한 6개 테스트 |
| search/related/count/undelete/access 대한 7개 테스트 |
| clone/validate/aggregate/runFlow/urls 대한 7개 테스트 |
| 인트/유틸리티 도구에 대한 11개 테스트(총 커버리지 약 97.5%) |
| MCP 서버定義: 27개의 Apex 액션을 액션으로 연결 (자체 패키지 디렉터리: 패키징 불가) |
| REST 를 통해 테스트 스크립트, 엔드투엔드 스모크 테스트(45개 검사) |
| 익명 Apex 스모크 스크립트 |
| 소스에서 |
|
|
| 한 번의 명령으로 설치 + 검증할 수 있는 최고의 유틸리티 (모든 org) |
| Scratch org 부트스트랩; |
| 모든 액션 클래스에 대한 액세스를 할 수 있음(객체 CRUD 없음) |
| MCP용 OAuth 클라ient: |
| CI: 테스트를 포함한 check-only 배포 및 문서 드리프트 검사 |
API 버전: 67.0 (sfdx-project.json). 호스팅된 MCP 서버와 McpServerDefinition 메타데이터를 사용할 수 있는 org가 필요합니다(Summer '26 또는 이후 버전, 최신 릴리스 노트에서 확인).
배포
# deploy everything and run the test class
sf project deploy start -o <alias> -d force-app/main/default \
-l RunSpecifiedTests -t SObjectActionsTest -t SObjectActionsExtTest -t SObjectActionsExt2Test -t SObjectActionsExt3Test -t SObjectActionsExt4Test
# Test fixtures (record types + dependent picklist used by two tests; deploy first for full per-class coverage)
sf project deploy start -o <alias> -d test-fixtures && sf org assign permset -n SObjectActions_Fixture -o <alias>
# MCP server definition and External Client App (separate dirs)
sf project deploy start -o <alias> -d mcp -d eca
# Apex only (skip the MCP definition)
sf project deploy start -o <alias> -d force-app/main/default/classes \
-l RunSpecifiedTests -t SObjectActionsTest -t SObjectActionsExtTest -t SObjectActionsExt2Test -t SObjectActionsExt3Test -t SObjectActionsExt4Test참고:
McpServerDefinition개발자 이름은 영숫자여야 하며, 문자로 시작해야 하고 2~40자여야 합니다(밑줄은 허용되지 않음). 이름은SObjectActions입니다.apiIdentifier값은aa:apex-<ClassName>형식이며, 클래스가 배포되면 API Catalog에서 자동으로 확인됩니다. 클래스와 정의를 함께 배포하거나 클래스를 먼저 배포하세요.
MCP 서버 활성화
설정 > MCP 서버 >
SObject Actions> 외부 클라이언트 앱 SObject Actions MCP Client(이 저장소에서 배포됨) 연결 > 활성화.클라이언트 인증 세부 정보:
docs/CLIENT_AUTH.md.사용자에게 Apex 클래스에 접근할 수 있는 권한 세트와 작업 대상 객체에 대한 CRUD/FLS 권한을 할당하세요. 도구는 인증된 사용자로 실행됩니다.
설정에 표시된 서버 URL을 MCP 클라이언트(Claude, Agentforce, Cursor 등)에 지정하세요.
tools/list는checkAccess,describeObject,searchRecords,findRecords,countRecords,aggregateRecords,readRecords,relatedRecords,recordSummary,picklistValues,validateRecords,createRecords,upsertRecords,updateRecords,cloneRecords,assignOwner,changeRecordType,deleteRecords,undeleteRecords,logActivity,closeCase,convertLead,postChatter,addNote,attachFile,listFlows,runFlow를 반환합니다.
정의에 설정된 도구 어노테이션:
도구 | readOnly | destructive | idempotent |
createRecords | false | false | false |
readRecords | true | false | true |
updateRecords | false | false | true |
deleteRecords | false | true | false |
upsertRecords | false | false | true |
findRecords | true | false | true |
describeObject | true | false | true |
searchRecords | true | false | true |
relatedRecords | true | false | true |
countRecords | true | false | true |
undeleteRecords | false | false | true |
checkAccess | true | false | true |
cloneRecords | false | false | false |
validateRecords | true | false | true |
aggregateRecords | true | false | true |
runFlow | false | false | false |
assignOwner | false | false | true |
changeRecordType | false | false | true |
picklistValues | true | false | true |
logActivity | false | false | false |
closeCase | false | false | true |
convertLead | false | false | false |
postChatter | false | false | false |
addNote | false | false | false |
attachFile | false | false | false |
listFlows | true | false | true |
recordSummary | true | false | true |
일반적인 에이전트 순서: describeObject(정확한 API 이름 확인) -> findRecords / readRecords -> createRecords / upsertRecords / updateRecords -> deleteRecords.
공통 규약
objectApiName
모든 작업에 필요합니다. 대소문자를 구분하지 않는 API 이름: Account, Case, Task, My_Object__c, ...
Activity(다형 Task/Event)
Activity는 직접 생성하거나 조회할 수 없으므로 "Task 또는 Event"의 별칭으로 처리됩니다.
입력 형식 | 구체적인 유형 결정 방식 |
| SObject의 실제 유형에서 결정하며, Task/Event가 아닌 유형은 거부됩니다. |
| 각 요소 반드시 |
| Id 접두어( |
따라서 objectApiName = "Activity"인 경우 단일 요청에 Task와 Event를 함께 포함할 수 있습니다.
레코드 입력(create / update)
원하는 조합을 제공할 수 있으며, 다음 순서로 병합됩니다:
필드 | 유형 | 대상 | 설명 |
| SObject | 흐름 |
|
| List<SObject> | 흐름 | 동일함. |
| String | MCP / REST |
|
recordsJson 규칙:
필드 이름은 객체에 존재해야 합니다(직접 필드 API 이름 또는 관계 이름, 외부 ID upsert의
Account등). 알 수 없는 이름은 조용히 무시되지 않고Unknown field(s) on <Object>: <names>오류로 요청 전체가 실패합니다.값은 플랫폼 JSON 역직렬화에 의해 변환됩니다: 날짜는
YYYY-MM-DD, 날짜/시간은 ISO-8601(2030-01-01T10:00:00.000Z), 부울은true/false, 숫자는 따옴표 없이 사용합니다.attributes.type이 있는 경우objectApiName(ActivityTransaction인 경우 Task/Event)과 일치해야 합니다.
Id 입력(read / delete)
recordId(읽기만) 및/또는recordIds(읽기 + 삭제). 빈 항목은 건너뜁니다.15자 또는 18자 Id 허용. 잘못된 문자열은 요청을 실패시킵니다(
Invalid record Id: ...).각 Id의 유형은
objectApiName과 일치해야 합니다(Activity인 경우 Task/Event).
allOrNone(create / update / delete)
기본값
false: 부분 성공. 실패한 각 row는errors에서row <n>: <message>로 보고됩니다.true: 어떤 실패가 있어도 요청 전체가 롤백됩니다.danger당failureCount= 행 수이고,errors에는 DML 예외 메시지가 담깁니다.
레이블
recordLabel은 recordIds와 인덱스가 일치합니다. 레이블 출처:
객체 | 레이블 |
Case |
|
기타 모든 객체 | 객체의 describe 이름 필드( |
이름 필드 없음 / 읽기 권한 없음 | 레코드 Id |
생성/업데이트 레이블은 DML 후 다시 쿼리됩니다(따라서 CaseNumber 같은 자동 번호가 채워집니다). 삭제 시 레이블은 삭제 전에 캡처됩니다.
공통 출력
필드 | 유형 | 의미 |
| Boolean | 요청의 모든 행이 성공한 경우에만 true(읽기: 모든 Id를 찾은 경우) |
| Integer | 행 수 |
| Integer | 행 수 |
| 객체<String> | 영향받은/찾은 Id, 입력 순서대로 |
| List<String> |
|
| List<String> | 실패한 각 행마다 |
| String | 한 줄 요약(예: |
| List<String> |
|
요청 수준 검증 오류(잘못된 객체 이름, 잘못된 형식의 JSON 등)는 isSuccess=false, failureCount=1, errors 하나의 항목으로 설정됩니다. 해당 요청은 DML을 실행하지 않고 동일 호출의 다른 요청은 영향을 받지 않습니다.
도구 참조
createRecords / SObjectCreateAction
Invocable 레이블: Create Records (Generic), 카테고리 SObject Actions.
입력: objectApiName(필수), record, records, recordsJson, allOrNone.
출력: isSuccess, successCount, failureCount, recordIds, recordLabels, errors, message.
MCP 호출 예시:
{ "objectApiName": "Account",
"recordsJson": "[{\"Name\":\"Acme\",\"Industry\":\"Energy\"},{\"Name\":\"Globex\"}]" }{ "objectApiName": "Case",
"recordsJson": "{\"Subject\":\"Printer on fire\",\"Status\":\"New\",\"Origin\":\"Web\",\"AccountId\":\"001...\"}" }-> recordLabels: ["00001234 - Printer on fire"]
{ "objectApiName": "Activity",
"recordsJson": "[{\"attributes\":{\"type\":\"Task\"},\"Subject\":\"Call\",\"WhatId\":\"001...\"},{\"attributes\":{\"type\":\"Event\"},\"Subject\":\"Meet\",\"DurationInMinutes\":30,\"ActivityDateTime\":\"2030-01-01T10:00:00.000Z\"}]" }샘플 결과:
{ "isSuccess": true, "successCount": 2, "failureCount": 0,
"recordIds": ["00T...", "00U..."], "recordLabels": ["Call", "Meet"],
"errors": [], "message": "2 created, 0 failed." }부분 실패(allOrNone=false):
{ "isSuccess": false, "successCount": 1, "failureCount": 1,
"recordIds": ["003..."], "recordLabels": ["Good"],
"errors": ["row 2: Required fields are missing: [LastName] [LastName]"],
"message": "1 created, 1 failed." }readRecords / SObjectReadAction
**vocable 레이블: Read Records (Generic).
입력:
필드 | 유형 | 설명 |
| String(필수) |
|
| String | 단일 Id |
| List<String> | 대량 동작; 중복된 Id는 하나로 통합됩니다. |
| List<String> | 선택 사항. 필드 API 이름을 나타내고(관계 경로 허용: |
출력:
only, no preamble.
Let's finalize.
Let's now write final text.
Potential issue: The first section's recordsJson note says "JSON array of found records (serialized SObjects, includes attributes.type) - MCP-facing". Translate "MCP에 노출되는 값" rather than "MCP-facing". What does "MCP-facing" mean? The final answer "MCP용" maybe. Let's use "MCP 대응". Actually "Flow-facing" in next row: "Flow향". We can say "MCP용" and "Flow용". Good.
First two cells:
"First / all found records as SObjects - Flow-facing" -> "첫 번째/모든 찾은 레코드를 SObject로 변환 - Flow용".
OK.
Now let's final| 필드 | 설명 |
|---|---|
| isSuccess | 요청한 모든 Id를 찾았고 표시할 수 있으면 true |
| foundCount, notFoundCount | |
| recordIds, recordLabels | 찾은 레코드, 입력 순서 |
| recordsJson | 찾은 레코드의 JSON 배열(직렬화된 SObject, attributes.type 포함) - MCP 대응 |
| record, records | 첫 번째/전체 찾은 레코드를 SObject로 - Flow 대응 |
| notFoundIds | 존자하지 않거나 sharing 권한상 볼 수 없는 Id |
| errors, message | |
예시:
{ "objectApiName": "Account", "recordId": "001..." }{ "objectApiName": "Case", "recordIds": ["500...","500..."], "fields": ["Status","Priority","Account.Name"] }{ "objectApiName": "Activity", "recordIds": ["00T...","00U..."], "fields": ["Subject","ActivityDate"] }샘플 결과:
{ "isSuccess": true, "foundCount": 1, "notFoundCount": 0,
"recordIds": ["500..."], "recordLabels": ["00001234 - Printer on fire"],
"recordsJson": "[{\"attributes\":{\"type\":\"Case\"},\"Id\":\"500...\",\"CaseNumber\":\"00001234\",\"Subject\":\"Printer on fire\",\"Status\":\"New\",\"Account\":{\"attributes\":{\"type\":\"Account\"},\"Name\":\"Acme\"}}]",
"notFoundIds": [], "errors": [], "message": "1 found, 0 not found." }직접 필드 이름은 리 전에 검즉됩니다 (Unknown field on Account: Foo__c).
관계 경로는 SOQL 자체가 검중하며, 잘못된 경로는 errors에 QueryException로 나타납니다.
사용자가 볼 수 없는 레코드는 (오류 없이) notFoundIds에 보고됩니다.
updateRecords / SObjectUpdateAction
호출 라벨: 레코드 업데이트 (Generic).
입력: objectApiName (필수), record, records, recordsJson, allOrNone.
모든 행에는 Id가 포함되어야 합니다. Id가 없는 행이 있으면 전체 요청이 실패합니다.
(row <n>: Id is required for update.).
출력: create와 동일한 형테 (successCount, recordIds, recordLabels, ...).
Label은 호출의 모드 업데이트가 적영된 이후의 레코드 상태를 반영합니다.
예시:
{ "objectApiName": "Account",
"recordsJson": "[{\"Id\":\"001...\",\"Name\":\"Acme Corp\",\"Phone\":\"555-0100\"}]" }{ "objectApiName": "Case", "recordsJson": "{\"Id\":\"500...\",\"Status\":\"Closed\"}" }{ "objectApiName": "Activity",
"recordsJson": "[{\"attributes\":{\"type\":\"Task\"},\"Id\":\"00T...\",\"Status\":\"Completed\"}]" }필드를 비우려면 null을 전달하세요: {"Id":"001...","Description":null}.
deleteRecords / SObjectDeleteAction
호출 라벨: 레코드 삭제 (Generic). 도구는 destructive로 표시됩니다.
입력: objectApiName (필수), recordIds, record, records, record allOrNone.
SObject레 전달되는 레코드에는 Id가 포◤되어야 합니다.
출력: recordIds, successCount, failureCount, recordIds, recordLabels
(삭제 전에 캡션), errors, message.
예시:
{ "objectApiName": "Account", "recordIds": ["001...", "001..."] }{ "objectApiName": "Activity", "recordIds": ["00T...", "00U..."] }삭제된 레코드는 사이클 빈으로 이동합니다 (표즁 Database.delete 의 맨스트 스). Cascade 삭제는 플랫폼의 master-detail / ookup 규칙을 따릅니다.
upsertRecords / ``SObjectUpsertAction`
호출 라벨: 레코드 Upsе р트 (Generic).
입력: objectaApiName (필수), externalIdField (선택, 기존 Id), record, records,
recordsJson, allOrNone. externalIdField는 객체의 external Id / idLookup 필드여야 합니다.
dates is supported (Task/Event via att objects.type); 외부 Id 필드는 구체적 타입에 조체해야 합니다.
출력: records, failureCount, createdCount, updatedCount, recordIds, recordLabels, wasCreated[] (recordIds와 정렬), errors, message.
{ "objectApiName": "Account", "externalIdField": "External_Key__c",
"recordsJson": "[{\"External_Key__c\":\"ERP-1001\",\"Name\":\"Acme\"},{\"External_Key__c\":\"ERP-1002\",\"Name\":\"Globex\"}]" }{ "objectApiName": "Contact",
"recordsJson": "[{\"Id\":\"003...\",\"Email\":\"a@b.com\"},{\"LastName\":\"New Person\"}]" }-> wasCreated: [false, true], message: "1 created, 1 updated, 0 failed."
행은 구체적 객 object type 객으로 그룹되며, 각 그룹은 하의 Database.upsert입니다.
allOrNone=true는 savepoint를 사용하므로 어느 그룹에서 실패해도 전제 요청이 rollback됩니다.
findRecords / SObjectFindAction
호출 라벨: 레코드 검찾 (Generic). 구조화된 filter만 지원합니다. 원시 SOQL/WHERER은 허용되지 않습니다.
입력:
Field | Notes | |
| Must be queryable. | |
|
| |
| 모든 필터에 적용 | |
| 반환할 추가 필드. 관계 경로 허용( | |
|
| DESC"`. |
| 1–200, 기본값 50. |
값은 바인딩 전에 필드 타입으로 변환됩니다(날짜 YYYY-MM-DD, 날짜시간 ISO-8601, 숫자, 부울, Id). 모든 값은 bind 변수로 전달됩니다(Database.queryWithBinds, USER_MODE).
출력: isSuccess, resultCount, recordIds, recordLabels, recordsJson, records (Flow),
soql (실행된 쿼리, 값은 :b0, :b1...로 마스킹됨), errors, message
(resultCount == limitCount이면 "..." (limit reached)"`).
예시:
{ "objectApiName": "Case",
"filtersJson": "[{\"field\":\"Status\",\"op\":\"IN\",\"value\":[\"New\",\"Working\"]},{\"field\":\"CreatedDate\",\"op\":\">=\",\"value\":\"2026-08-01T00:00:00Z\"}]",
"fields": ["Status","Priority","Account.Name"], "orderBy": "CreatedDate DESC", "limitCount": 25 }{ "objectApiName": "Account",
"filtersJson": "[{\"field\":\"Name\",\"op\":\"LIKE\",\"value\":\"Acme%\"},{\"field\":\"Industry\",\"op\":\"=\",\"value\":\"Energy\"}]",
"filterLogic": "OR" }{ "objectApiName": "Task",
"filtersJson": "[{\"field\":\"WhatId\",\"op\":\"=\",\"value\":\"001...\"},{\"field\":\"IsClosed\",\"op\":\"=\",\"value\":false}]" }describeObject / SObjectDescribeAction
호출라벨: 객체 설명 (Generic). 하나의 도구에 두 가지 모드가 있습니다.
설명 모드 (objectApiName 설정):
Input | Notes |
|
|
| 기본값 true |
| 정 대 소문자를 구분하지 않는 필터 (API 이름 또는 레이블) |
출력: objectApiName, objectLabel, keyPrefix, isCustom, only Create/Updateable/Deleteable/Queryable, labelFields (예: Case의 경우 ["CaseNumber","Subject"]), requiredFields (create 가능, null 불가, default 없음), fieldsJson (접근 가능한 필드만: apiName, label, type, required, createable, updateable, externalId, nameField, length, referenceTo[], relationshipName, picklistValues[]), recordTypesJson (active, available, non-master: id, developerName, name, isDefault), childRelationshipsJson (relationshipName, childObject, field), resultCount (반환된 필드 수).
목록 모드 (objectApiName 비어 있음):
Input | Input | Notes |
| 정 API 이름 또는 레이블에 대해 대소문자 구분 없는 필터 | |
| 기본값 false |
출력: objectsJson (접근 가능한 객체의 apiName, label, keyPrefix, isCustom, createable, queryable; custom settings 및 prefix 없는 system object 제외), resultCount.
예시:
{ "objectApiName": "Case", "fieldNameContains": "status" }{ "objectApiName": "My_Object__c" }{ "objectNameContains": "invoice", "customOnly": true }searchRecords / SObjectSearchAction
호출 라벨: 레코드 검색 (Generic). SOSL 전문 검색이며, 용어는 바인딩됩니다 (FIND :term).
Input | Notes |
| 최소 2자; |
| 기본값 |
|
|
| 추가 필드. 해당 필드가 객체에 존자하는 경우에만 적용됩니다 |
| 만들기원천 object, 1–200, 기본값 20 |
출력: resultCount, recordIds, recordLabels, recordObjectNames (정렬됨), recordsObject, records.
{ "searchTerm": "acme*", "objectApiNames": ["Account","Contact"], "searchIn": "NAME", "fields": ["Phone","Email"] }relatedRecords / SObjectRelatedAction
호출 라벨: 관련 레코드 가져오기 (Generic).
Input | Notes |
| 부모 object는 Id에서 추론됩니다 |
| 부모의 하위 relationship name ( |
| parent `findRecords와 같이 동일 (기본 50, 최대 200) |
출력: parentObjectApiName, childObjectApiName, resultCount, recordIds, recordLabels, recordsJson, records:
{ "parentRecordId": "001...", "relationshipName": "Cases", "fields": ["Status","Priority"], "orderBy": "CreatedDate DESC", "limitCount": 10 }countRecords / SObjectCountAction
호출 라벨: 레코드 계수 (Generic).
Input | Notes |
| Task/Event, not Activity |
| `findRecords와 동일 |
| optional grouping field; up to 200 groups, sorting by count desc; null group is reported as |
출력: totalCount, recordValues[], groupCounts[] (aligned), groupsJson ([{value,count}]), soql.
{ "objectApiName": "Case", "filtersJson": "[{\"field\":\"IsClosed\",\"op\":\"=\",\"value\":false}]", "groupByField": "Priority" }undeleteRecords / SObjectUndeleteAction
호출 라벨: 레코드 복구 (Generic). Zicap에서 복원합니다.
입력: objectApiName (필수; Task/Event 혼합의 경우 Activity), recordIds (필수), allOrNone.
출력: successCount, failureCount, recordIds, recordLabels (복원 후), errors, message.
한 요청에 중복된 Id는 병합됩니다. 이미 복원되었거나 삭제된 레코드는 행 단위로 실패합니다.
{ "objectApiName": "Account", "recordIds": ["001..."] }checkAccess / SObjectAccessAction
호출 가능 레이블: Check Access (Generic). 모든 입력은 선택 사항이며, 입력이 없으면 "who am I"로 동작합니다.
입력 | 참고 |
| CRUD 확인 ( |
|
|
| 최대 200개; |
출력: userId, userName, loginUsername, profileId, profileName, userType, organizationId, timeZone,
objectApiName, canCreate/canRead/canUpdate/canDelete, fieldAccessJson, recordAccessJson, message.
{ "objectApiName": "Opportunity", "fields": ["Amount","StageName"], "recordIds": ["006..."] }cloneRecords / SObjectCloneAction
호출 가능 레이블: Clone Records (Generic).
입력 | 참고 |
| Task/Event 혼합의 경우 |
| 원본; 찾을 수 없거나 보이지 않는 Id는 행 단위로 실패 |
| 모든 복제본에 적용되는 JSON 객체 (필드 이름 검증됨) |
| 복사하지 않을 필드 (예: |
|
생성 가능하고, 읽기 가능하며, 자동 번호가 아니고, 수식이 아닌 모든 필드를 복사합니다. 하위 레코드는 복제되지 않습니다.
출력: sourceRecordIds (정렬됨), recordIds, recordLabels, recordUrls, 개수, errors.
{ "objectApiName": "Opportunity", "recordIds": ["006..."], "overridesJson": "{\"Name\":\"Renewal 2027\",\"StageName\":\"Prospecting\"}", "excludeFields": ["OwnerId"] }validateRecords / SObjectValidateAction
호출 가능 레이블: Validate Records (Generic, dry run). 저장점(savepoint) 내에서 insert/update를 수행하고 항상 롤백합니다. 트리거, 유효성 검사 규칙, 필수 필드, FLS 및 공유는 실제로 모두 실행됩니다.
입력: objectApiName (필수), operation (CREATE 기본값 | UPDATE), record, records, recordsJson.
출력: isSuccess (모든 행이 저장될 수 있는지), successCount, failureCount, rowResultsJson
([{row, valid, errors[]}]), errors, message (... Nothing was saved.).
참고: DML은 여전히 가버너 한도에 포함되며, 트랜잭션에서 소비된 자동 번호는 재사용되지 않습니다.
{ "objectApiName": "Contact", "recordsJson": "[{\"LastName\":\"Ok\"},{\"FirstName\":\"No last name\"}]" }aggregateRecords / SObjectAggregateAction
호출 가능 레이블: Aggregate Records (Generic).
입력 | 참고 |
| Task/Event, Activity 아님 |
|
|
| findRecords와 동일 |
| 0-3개의 그룹화 가능한 필드 |
| 그룹에만 적용, 1-2000, 기본값 200 (그룹화되지 않은 쿼리는 LIMIT를 사용할 수 없음) |
출력: resultCount, rowsJson (각 행: 그룹 필드 값 + FUNCTION_Field 키, 예: SUM_Amount), soql.
{ "objectApiName": "Opportunity",
"aggregationsJson": "[{\"function\":\"SUM\",\"field\":\"Amount\"},{\"function\":\"COUNT\",\"field\":\"Id\"}]",
"filtersJson": "[{\"field\":\"IsClosed\",\"op\":\"=\",\"value\":false}]",
"groupByFields": ["StageName"] }runFlow / SObjectRunFlowAction
호출 가능 레이블: Run Flow (Generic).
입력 | 참고 |
| 활성화된 자동 실행 플로우 |
| 입력 변수 -> 값의 JSON 객체 |
| 반환할 "출력 가능"으로 표시된 변수 |
출력: interviewId, outputsJson, errors (플로우 오류는 Could not start flow ... 또는 오류 메시지로 표시됨), message. 플로우는 동일한 트랜잭션에서 플로우가 선언한 실행 모드로 실행됩니다.
{ "flowApiName": "SObjectActions_EchoFlow", "inputsJson": "{\"inputText\":\"hi\",\"inputNumber\":1}", "outputVariableNames": ["outputText","outputNumber"] }assignOwner / SObjectAssignOwnerAction
입력: objectApiName, recordIds, newOwnerId (005/00G) 또는 newOwnerName (정확한 사용자 전체 이름 / 사용자 이름, 또는 큐 이름 / 개발자 이름; 모호한 이름은 거부됨), allOrNone.
출력: 확인된 ownerId/ownerName, 레코드별 개수, recordIds, recordLabels, errors.
{ "objectApiName": "Case", "recordIds": ["500..."], "newOwnerName": "Tier 2 Support" }changeRecordType / SObjectRecordTypeAction
입력: objectApiName, recordIds, recordType (Id, DeveloperName 또는 Name), allOrNone. 오류에는 사용 가능한 개발자 이름이 나열됩니다. 출력: 확인된 recordTypeId/recordTypeName, 레코드별 결과.
picklistValues / SObjectPicklistAction
입력: objectApiName, fieldApiName, includeInactive. 출력: values[], labels[], valuesJson
({value,label,active,default,validFor[]}), isRestricted, isDependent, controllingField, defaultValue.
값은 조직 전체 필드 정의를 반영합니다 (레코드 유형별 값 집합은 적용되지 않음).
logActivity / SObjectLogActivityAction
완료된 Task를 기록합니다. 입력: subject (필수), relatedRecordId (WhatId), personRecordId (Contact/Lead WhoId), description, activityDate (기본값 오늘), activityType, status (기본값 첫 번째 완료 상태), priority, ownerId, extraFieldsJson. 출력: recordId, recordUrl, status.
{ "subject": "Call with CFO", "relatedRecordId": "001...", "personRecordId": "003...", "description": "Discussed renewal", "activityType": "Call" }closeCase / SObjectCloseCaseAction
입력: caseIds, status (닫힌 상태여야 함; 기본값 첫 번째 닫힌 상태), comment (+ commentIsPublic), extraFieldsJson, allOrNone. 출력: 사용된 status, 케이스별 개수, recordLabels (CaseNumber - Subject).
convertLead / SObjectConvertLeadAction
입력: leadId, convertedStatus (기본값 첫 번째 변환된 상태), accountId / contactId (기존 레코드에 병합), createOpportunity (기본값 true), opportunityName, ownerId, sendEmailToOwner. 출력: accountId, contactId, opportunityId + URL.
postChatter / SObjectPostChatterAction
입력: recordId (피드가 활성화된 레코드 또는 User), text, mentionUserIds. 출력: feedItemId. 객체에 피드 추적이 없으면 명확한 메시지와 함께 실패합니다.
addNote / SObjectAddNoteAction
입력: title, body (일반 텍스트 또는 간단한 HTML), recordIds, shareType (V 기본값 / I / C). ContentNote 및 ContentDocumentLink를 생성합니다. 조직에서 향상된 메모(Enhanced Notes)가 비활성화된 경우 메모를 .html 파일로 저장합니다 (storedAsFile=true). 출력: noteId, 연결된 recordIds, storedAsFile.
attachFile / SObjectAttachFileAction
입력: fileName, textContent 또는 base64Content, title, recordIds, shareType. 출력: contentVersionId, contentDocumentId, 연결된 recordIds. 큰 base64 페이로드에는 힙 제한이 적용됩니다 (동기식 약 6MB).
listFlows / SObjectListFlowsAction
목록 모드: nameContains, processType (기본값 AutoLaunchedFlow; ALL), includeInactive, limitCount -> flowsJson.
상세 모드: flowApiName -> variablesJson (apiName, dataType, isInput, isOutput, isCollection, objectType, description). runFlow와 함께 사용합니다.
recordSummary / SObjectSummaryAction
입력: recordId, fields (기본값 모든 접근 가능 필드), relationshipNames (최대 10개; 기본값 존재하는 일반적인 관계), recentActivityLimit (0-20, 기본값 5).
출력: objectApiName, recordLabel, recordUrl, ownerName, recordJson, relatedCountsJson ({Contacts: 3, Cases: 1, ...}), recentActivityJson ([{id,type,subject,date,status,ownerName}]).
레코드 1개에 SOQL 1회 + 관계당 1회 + 활동에 2회가 소요됩니다.
Flow에서 호출
Action 요소를 추가하고 SObject Actions 카테고리를 검색한 후 작업을 선택합니다.
Object API Name을 리터럴 또는 텍스트 변수로 설정합니다.
create/update의 경우 레코드 변수를 Record에, 레코드 컬렉션을 Records에 할당합니다. Flow의 일반 SObject 입력은 작업 요소에서 객체 유형을 선택하도록 요구합니다.
objectApiName과 일치해야 합니다 (또는Activity와 함께 Task/Event).출력
Created Record Ids/Record/Records/Errors를 Flow 변수로 읽습니다.부분 성공을 원하면 All Or None을 비워 두거나, 롤백하려면
{!$GlobalConstant.True}로 설정합니다.
오류 경로: 요청 수준 문제는 예외를 발생시키지 않습니다. Success 및 Errors를 확인하세요.
예기치 않은 플랫폼 예외(한도 등)만 Flow 오류 커넥터에 도달합니다.
REST에서 호출
POST /services/data/v67.0/actions/custom/apex/SObjectCreateAction
Authorization: Bearer <token>
Content-Type: application/json
{ "inputs": [
{ "objectApiName": "Account", "recordsJson": "[{\"Name\":\"Acme\"}]" },
{ "objectApiName": "Contact", "recordsJson": "[{\"LastName\":\"Smith\",\"AccountId\":\"001...\"}]" }
] }SObjectReadAction, SObjectUpdateAction, SObjectDeleteAction에도 동일한 형태가 적용됩니다.
inputs의 각 요소는 응답 outputValues의 한 요소에 매핑됩니다.
보안 모델
모든 DML 및 SOQL은
AccessLevel.USER_MODE로 실행됩니다. 객체 CRUD, 필드 수준 보안 및 실행 중인 사용자의 공유가 플랫폼에 의해 적용됩니다.쓰기 불가능한 필드의 생성/업데이트 -> 플랫폼에서 행 오류 발생.
접근 불가능한 객체/필드 읽기 -> 반환되지 않음 (기본 필드 목록에는 접근 가능한 필드만 포함되며, 명시적으로 접근 불가능한 필드는 쿼리 오류 발생).
사용자의 공유 범위 밖의 레코드 -> 읽기 시
notFoundIds, 업데이트/삭제 시 행 오류.
클래스는
global with sharing입니다 (MCP 검색에 필요).원시 호출자 입력에서
Database.query문자열이 생성되지 않습니다. 객체 이름은 describe를 통해, 필드 이름은 필드 맵을 통해, Id는Id.valueOf를 통해 검증됩니다.readRecords.fields의 관계 경로는 SELECT 목록에만 배치되며 WHERE 절에는 절대 배치되지 않습니다.도구가 일반적이므로 권한 집합으로 접근을 제어하세요: Apex 클래스를 호출할 수 있는 사람과 객체에 대한 CRUD/FLS를 결정합니다. 에이전트 사용 사례에서 삭제가 절대 발생하지 않아야 하는 경우 서버에서
deleteRecords를 제외하는 것을 고려하세요.
한도 및 대량 동작
호출 가능 입력은 대량 처리됩니다. 한 번의 호출에서 모든 요청의 모든 레코드는 가능한 가장 적은 수의 DML 문으로 결합됩니다 (일반적인 경우 호출당 하나의 insert / update / delete).
한 번의 호출에서 동일한 레코드가 두 번 업데이트/삭제되는 중복 Id는
Duplicate id in list오류 대신 순차 DML 배치로 분할됩니다.레이블 조회: 호출당 구체적인 객체 유형별 SOQL 1회 (create/update/delete); 읽기는 요청당 구체적인 유형별 SOQL 1회.
트랜잭션당 적용되는 가버너 한도: SOQL 100개, DML 문 150개, DML 행 10,000개, 힙 6MB (동기식). 매우 큰
recordsJson페이로드 또는 넓은 객체에서fields없이 수백 개의 Id를 읽는readRecords는 힙/CPU 한도에 근접할 수 있습니다. 대량 읽기에는 명시적fields목록을 전달하세요.한 번의 호출에서 여러 객체 유형을 혼합하는 것은 괜찮지만, 설정 객체와 비설정 객체 (예: User + Account)를 단일 호출에 혼합하면 플랫폼의 혼합 DML 규칙이 적용됩니다.
서로 다른 SObject 유형의 DML 청크당 레코드 수는 플랫폼에 의해 DML 문당 최대 10개의 고유 유형으로 제한됩니다.
오류 카탈로그
메시지 | 원인 | 해결 방법 |
| 입력 값이 누락됨 | API 이름을 제공하세요 |
| 오타/존재하지 않는 이름 | 철자와 오브젝트 접근 권한을 확인하세요 |
|
|
|
| 잘못된 유형의 SObject 입력 | 입력 유형을 일치시키세요 |
| JSON 행이 다른 유형으로 지정됨 |
|
| 형식이 잘못된 문자열 | 적절히 이스케이프된 JSON 객체 또는 배열인지 확인하세요 |
| 스칼라/기타 JSON |
|
| 오브젝트에 없는 필드 이름 | API 이름을 수정하세요 ( |
| 값 유형 변환 실패(잘못된 날짜 형식 등) | ISO-8601 날짜/날짜시간을 사용하고 올바른 유형을 지정하세요 |
| 작업 대상이 없음 |
|
| 유효한 15/18자리 Id가 아님 | Id를 수정하세요 |
| 잘못된 접두어 | objectApiName 또는 Id를 수정하세요 |
| Id가 없는 업데이트 행 |
|
| Id가 없는 SObject 삭제 입력 |
|
|
| API 이름을 수정하세요 |
| 잘못된 | 외부 Id 필드를 사용하세요 (또는 |
| 잘못된 점(.) 포함 필터 필드 | 직접 필드를 사용하세요 |
| 긴 텍스트/암호화 필드 등 | 다른 필드로 필터링하세요 |
| 잘못된 |
|
| 유형 변환 실패 | 필드 유형/ISO 날짜 형식과 일치시키세요 |
| 범위를 벗어남 | 조정하세요 |
| DML 실패(검증 규칙, 필수 필드, FLS, 공유 설정) | 데이터 또는 권한을 수정하세요 |
테스트
세 가지 계층으로 구성하며, 가장 저렴한 것부터입니다.
# 1. Unit tests (Apex, ~98% coverage)
sf apex run test -o <alias> -n SObjectActionsTest -n SObjectActionsExtTest -n SObjectActionsExt2Test -n SObjectActionsExt3Test -n SObjectActionsExt4Test -r human -w 10 -c
# 2. Anonymous Apex smoke: create -> read -> update -> delete, prints every result
sf apex run -o <alias> -f scripts/apex/smoke.apex
# 3. REST smoke through the Invocable Actions API (the exact path MCP tools use).
# 45 assertions across all 27 tools incl. Case labels, Activity Task/Event mix,
# validation errors. Creates and removes its own records. Needs jq.
scripts/smoke-test.sh <alias> [apiVersion]MCP 계층 자체를 테스트하려면 위 안내대로 서버를 활성화하고, External Client App으로 MCP 클라이언트를 연결한 다음 tools/list를 실행하고 예를 들어 readRecords {"objectApiName":"Account","recordId":"0011..."} 호출합니다.
테스트 범위: 생성(SObject + JSON 입력, Case 라벨, Activity 해석, 검증 매트릭스, 부분 및 all-or-none), 조회(전체/명시적 필드, 관계 경로, Activity 혼합, 조회 결과 없음, 검증), 업데이트(JSON/SObject/SObject/Activity 혼합, Id 누락, 유령 Id, all-or-none 롤백, 요청 간 중복 Id), 삭제(Ids/SObjects/Activity 혼합, 라벨, 검증, 부분, all-or-none, 중복 Id), 유틸리티 대체 동작.
테스트는 테이블 개수가 아닌 레코드 Id 기준으로 검증하므로, Account에 기존 트리거나 자동화가 있는 조직에서도 통과합니다.
확장
원래 설계 목록의 모든 항목이 구현되어 있습니다. 자연스러운 다음 추가 사항은 레코드 유형 인식 선택 목록(Named Credential을 통한 UI API 호출), sendEmail(단일 이메일/템플릿), approval submit/recall, 공유/공유 해제(수동 공유) 작업입니다.
사용자 정의 라벨 규칙:
SObjectActionUtil.labelFields()를 확장합니다(Case가 현재 특별 사례입니다).서버의 새 도구 추가:
mcp/main/default/mcpServerDefinitions/SObjectActions.example.mcpServerDefinition-meta.xml에apiIdentifier = aa:apex-<ClassName>과 함께<tools>블록을 추가하고 다시 배포하세요. 필요하면 Set up에서 서버를 다시 활성화하세요.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to interact with Salesforce organizations through natural language by exposing Salesforce APIs (REST, Bulk v2, GraphQL, Tooling, Auth) as MCP tools for querying data, managing records, and executing SOQL queries.1219MIT
- FlicenseNot gradedqualityBmaintenanceEnables interaction with Salesforce data and services via custom MCP tools, including account analytics, opportunity queries, case creation, and AI agent invocation.4
- AlicenseNot gradedqualityBmaintenanceProvides live-org context for AI assistants with tools to search skills, agents, templates, decision trees, and query Salesforce metadata (Apex, LWC, objects, fields, etc.) via MCP.15Apache 2.0
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to interact with Salesforce through MCP, supporting queries, records, metadata, and bulk operations with flexible OAuth authentication.MIT
Related MCP Connectors
Search, document and execute authenticated API calls across 700+ apps via one MCP server
An AI concierge that turns static forms into adaptive AI conversations. From any MCP client.
Operator-as-agent MCP hub. 6 tools. First $5 free, then $0.001/call.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Kelley-Austin/sfka-mcp-demo'
If you have feedback or need assistance with the MCP directory API, please join our Discord server