salesforce-metadata-mcp 2.1.3 → 2.1.4
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/AGENTFORCE.md +125 -1
- package/CHANGELOG.md +44 -0
- package/dist/schemas/index.js +50 -3
- package/dist/services/deployment.js +5 -2
- package/dist/services/salesforce.js +333 -1
- package/dist/tools/agentforce.js +195 -36
- package/package.json +1 -1
package/AGENTFORCE.md
CHANGED
|
@@ -167,6 +167,124 @@ You must deactivate an agent before modifying its topics, actions, or planner:
|
|
|
167
167
|
|
|
168
168
|
After making changes, re-activate with `sf_activate_agent`.
|
|
169
169
|
|
|
170
|
+
> **Note:** `sf_update_agent_topic` handles deactivate/reactivate automatically.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Listing and Inspecting Agents
|
|
175
|
+
|
|
176
|
+
### List all agents (`sf_list_agents`)
|
|
177
|
+
|
|
178
|
+
No parameters required. Returns all agents with status:
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
sf_list_agents → { agents: [{ apiName, label, status, lastModifiedDate }] }
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Get full agent config (`sf_get_agent`)
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{ "agentApiName": "CRM_Record_Creator" }
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Returns activation status, all topics (GenAiPlugin), actions (GenAiFunction), and planner bundles (GenAiPlannerBundle) in the org.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Updating Topics and Actions
|
|
195
|
+
|
|
196
|
+
### Update a topic in-place (`sf_update_agent_topic`)
|
|
197
|
+
|
|
198
|
+
Automatically deactivates the agent, updates the GenAiPlugin, then reactivates:
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"agentApiName": "CRM_Record_Creator",
|
|
203
|
+
"topicName": "Account_Management",
|
|
204
|
+
"label": "Account Management",
|
|
205
|
+
"description": "Handle requests to create, update, or find Account records",
|
|
206
|
+
"scope": "Creating, updating, or looking up company/account information",
|
|
207
|
+
"instructions": ["Ask for company name", "Collect optional fields", "Call the appropriate action", "Confirm success"],
|
|
208
|
+
"actions": ["Create_Account", "Update_Account"],
|
|
209
|
+
"escalationEnabled": false
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Update an action (`sf_update_agent_action`)
|
|
214
|
+
|
|
215
|
+
Redeploys the GenAiFunction bundle with new schema files:
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"actionName": "Create_Account",
|
|
220
|
+
"label": "Create Account",
|
|
221
|
+
"description": "Creates a new Account record with company name, industry, phone, and website",
|
|
222
|
+
"invocationTargetType": "Flow",
|
|
223
|
+
"flowApiName": "Create_Account_Record_v2",
|
|
224
|
+
"inputs": [
|
|
225
|
+
{ "name": "AccountName", "label": "Account Name", "type": "Text", "required": true },
|
|
226
|
+
{ "name": "AnnualRevenue", "label": "Annual Revenue", "type": "Currency", "required": false }
|
|
227
|
+
],
|
|
228
|
+
"outputs": [
|
|
229
|
+
{ "name": "AccountId", "label": "Account ID", "type": "Text" }
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Apex-backed actions
|
|
235
|
+
|
|
236
|
+
Set `invocationTargetType: "ApexClass"` and provide `apexClassName` instead of `flowApiName`:
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
{
|
|
240
|
+
"actionName": "Search_Knowledge",
|
|
241
|
+
"label": "Search Knowledge Base",
|
|
242
|
+
"description": "Searches the knowledge base for articles matching the query",
|
|
243
|
+
"invocationTargetType": "ApexClass",
|
|
244
|
+
"apexClassName": "KnowledgeSearchAction",
|
|
245
|
+
"inputs": [{ "name": "searchQuery", "label": "Search Query", "type": "Text", "required": true }],
|
|
246
|
+
"outputs": [{ "name": "articleBody", "label": "Article Body", "type": "TextArea" }]
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Deleting Agents (`sf_delete_agent`)
|
|
253
|
+
|
|
254
|
+
```json
|
|
255
|
+
{
|
|
256
|
+
"agentApiName": "CRM_Record_Creator",
|
|
257
|
+
"deleteTopics": true,
|
|
258
|
+
"deleteActions": true
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
- `deleteTopics: true` — also deletes the agent's GenAiPlugin topics (discovered via the planner bundle)
|
|
263
|
+
- `deleteActions: true` — also deletes the GenAiFunction actions referenced by those topics
|
|
264
|
+
- Without flags, only the Bot and GenAiPlannerBundle are deleted
|
|
265
|
+
|
|
266
|
+
**WARNING:** Deleted topics and actions affect all agents that reference them.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Testing Agents (`sf_test_agent`)
|
|
271
|
+
|
|
272
|
+
Send a test message without opening the Salesforce UI:
|
|
273
|
+
|
|
274
|
+
```json
|
|
275
|
+
{
|
|
276
|
+
"agentApiName": "CRM_Record_Creator",
|
|
277
|
+
"message": "Create an account called Acme Corp in the Technology industry"
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
- `response` — the agent's reply text
|
|
283
|
+
- `sessionId` — the session created (for follow-up messages)
|
|
284
|
+
- `success` — whether the full API round-trip succeeded
|
|
285
|
+
|
|
286
|
+
The agent must be **Active** before testing.
|
|
287
|
+
|
|
170
288
|
---
|
|
171
289
|
|
|
172
290
|
## End-to-End Example
|
|
@@ -210,7 +328,11 @@ If the agent routes correctly but never invokes actions (agent says "I can't do
|
|
|
210
328
|
|
|
211
329
|
2. **Check type mapping** — If the action is invoked but the flow fails with "field in incorrect format", the `type` in your action inputs doesn't match the flow variable type. A Currency flow variable needs `"type": "Currency"`, not `"type": "Text"`.
|
|
212
330
|
|
|
213
|
-
3. **
|
|
331
|
+
3. **Use `sf_get_agent_logs`** — Queries ConversationDefinitionEventLog automatically:
|
|
332
|
+
```json
|
|
333
|
+
{ "agentApiName": "CRM_Record_Creator", "limit": 50, "hoursBack": 2 }
|
|
334
|
+
```
|
|
335
|
+
Or run SOQL manually:
|
|
214
336
|
```sql
|
|
215
337
|
SELECT EventLabel, EventTarget, EventDetails
|
|
216
338
|
FROM ConversationDefinitionEventLog
|
|
@@ -221,3 +343,5 @@ If the agent routes correctly but never invokes actions (agent says "I can't do
|
|
|
221
343
|
Look for `TopicClassificationSuccess` (routing worked) vs `ActionExecuted` (action ran).
|
|
222
344
|
|
|
223
345
|
4. **Verify planner exists** — If `TopicClassificationSuccess` never appears, the GenAiPlannerBundle may be missing. Use `sf_create_agent_planner` to create it.
|
|
346
|
+
|
|
347
|
+
5. **Use `sf_test_agent`** — Send a test message programmatically and inspect the response without opening the Salesforce UI.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.1.4] - 2026-05-28
|
|
4
|
+
|
|
5
|
+
### New Tools — Agentforce CRUD & Debugging
|
|
6
|
+
|
|
7
|
+
#### Read
|
|
8
|
+
- `sf_get_agent` — Retrieve full agent configuration from org including activation status, all topics (GenAiPlugin), actions (GenAiFunction), and planner bundles (GenAiPlannerBundle)
|
|
9
|
+
- `sf_list_agents` — List all Agentforce agents in the org with name, label, activation status, and last modified date
|
|
10
|
+
|
|
11
|
+
#### Update
|
|
12
|
+
- `sf_update_agent_topic` — Update an existing GenAiPlugin topic in-place. Automatically deactivates the agent before the update and reactivates it after. All provided fields replace existing values.
|
|
13
|
+
- `sf_update_agent_action` — Update an existing GenAiFunction and regenerate its LLM-facing schema files via zip deploy. All provided fields replace existing values.
|
|
14
|
+
|
|
15
|
+
#### Delete
|
|
16
|
+
- `sf_delete_agent` — Cleanly delete a Bot + its GenAiPlannerBundle + optionally its GenAiPlugin topics + GenAiFunction actions. Discovers related components by traversing Bot → planner → topics → actions. Deactivates first to avoid dependency errors.
|
|
17
|
+
|
|
18
|
+
#### Test & Debug
|
|
19
|
+
- `sf_test_agent` — Send a test message to an agent via the Einstein Agent API (bootstrap → create session → send message → return response text). Full end-to-end test without opening the Salesforce UI.
|
|
20
|
+
- `sf_get_agent_logs` — Query ConversationDefinitionEventLog for agent debug info. Shows TopicClassificationSuccess, ActionExecuted, and error events. Includes summary counts and structured log entries. Filter by agent name, limit, and hours-back window.
|
|
21
|
+
|
|
22
|
+
### Enhanced Existing Tools
|
|
23
|
+
|
|
24
|
+
#### `sf_create_agent`
|
|
25
|
+
- Added `systemPrompt` optional param — custom system prompt injected into every conversation
|
|
26
|
+
- Added `openingMessage` optional param — welcome message shown when users first open the agent
|
|
27
|
+
|
|
28
|
+
#### `sf_create_agent_action` / `sf_update_agent_action`
|
|
29
|
+
- Added `invocationTargetType` param (`"Flow"` | `"ApexClass"`, default `"Flow"`) — support for Apex class actions in addition to AutoLaunchedFlows
|
|
30
|
+
- Added `apexClassName` optional param — Apex class API name for ApexClass-backed actions
|
|
31
|
+
- `flowApiName` is now optional (required only when `invocationTargetType` is `"Flow"`)
|
|
32
|
+
|
|
33
|
+
#### `sf_create_agent_topic` / `sf_update_agent_topic`
|
|
34
|
+
- Added `escalationEnabled` optional boolean — sets `canEscalate` on the GenAiPlugin (allows human agent escalation)
|
|
35
|
+
- Added `fallbackTopic` optional string — API name of a fallback GenAiPlugin topic
|
|
36
|
+
|
|
37
|
+
#### `sf_create_agent_planner`
|
|
38
|
+
- Added `dataLibraryName` optional param — API name of a Data Library (Knowledge Base) for Knowledge-grounded agents
|
|
39
|
+
|
|
40
|
+
### New SOAP Helpers (internal)
|
|
41
|
+
- `readMetadataItem(type, fullName)` — SOAP readMetadata for a single component
|
|
42
|
+
- `listMetadataType(type)` — SOAP listMetadata for all components of a type
|
|
43
|
+
- `deleteMetadataItems(type, fullNames[])` — SOAP deleteMetadata for one or more components
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
3
47
|
## [2.1.3] - 2026-05-28
|
|
4
48
|
|
|
5
49
|
### Bug Fixes — Agentforce Action Invocation
|
package/dist/schemas/index.js
CHANGED
|
@@ -862,6 +862,8 @@ export const CreateAgentSchema = z.object({
|
|
|
862
862
|
label: z.string().min(1).max(255).describe("Agent display label, e.g. 'CRM Record Creator'"),
|
|
863
863
|
description: z.string().max(1000).optional().describe("Agent description"),
|
|
864
864
|
type: z.enum(["EinsteinCopilot", "ExternalCopilot"]).default("EinsteinCopilot").describe("Bot type: 'EinsteinCopilot' for internal employee agents (shown in Lightning sidebar), 'ExternalCopilot' for customer-facing Agentforce Service Agents. CANNOT be changed after creation."),
|
|
865
|
+
systemPrompt: z.string().max(32000).optional().describe("Custom system prompt to override the agent's default behavior and persona. Injected into the agent's context before every conversation."),
|
|
866
|
+
openingMessage: z.string().max(2000).optional().describe("Welcome message shown to users when they first open the agent (set as welcomeMessage on the Bot)."),
|
|
865
867
|
}).strict();
|
|
866
868
|
export const CreateAgentTopicSchema = z.object({
|
|
867
869
|
topicName: z.string().min(1).max(80).regex(/^[A-Za-z][A-Za-z0-9_]*$/).describe("Topic API name, e.g. 'Create_Account_Topic'. Used as the GenAiPlugin developer name."),
|
|
@@ -870,6 +872,8 @@ export const CreateAgentTopicSchema = z.object({
|
|
|
870
872
|
scope: z.string().min(1).max(5000).describe("What this topic covers in detail, e.g. 'Create new Account records with company name, industry, phone, and website'"),
|
|
871
873
|
instructions: z.array(z.string().max(2000)).optional().describe("Ordered step-by-step instructions for the agent, e.g. ['Ask the user for the account name', 'Call the Create Account action', 'Confirm success to the user']"),
|
|
872
874
|
actions: z.array(z.string().max(80)).optional().describe("Action API names (GenAiFunction developer names) to associate, e.g. ['Create_Account']. Create actions first with sf_create_agent_action."),
|
|
875
|
+
escalationEnabled: z.boolean().optional().describe("Whether this topic can escalate to a human agent (sets canEscalate on GenAiPlugin). Default: false."),
|
|
876
|
+
fallbackTopic: z.string().max(80).optional().describe("API name of another GenAiPlugin topic to fall back to if this topic cannot handle the request."),
|
|
873
877
|
}).strict();
|
|
874
878
|
const AgentParamSchema = z.object({
|
|
875
879
|
name: z.string().max(80).describe("Parameter API name matching the flow variable name, e.g. 'AccountName'"),
|
|
@@ -882,15 +886,58 @@ export const CreateAgentActionSchema = z.object({
|
|
|
882
886
|
actionName: z.string().min(1).max(80).regex(/^[A-Za-z][A-Za-z0-9_]*$/).describe("Action API name (GenAiFunction developer name), e.g. 'Create_Account'. Used to reference this action from topics."),
|
|
883
887
|
label: z.string().min(1).max(255).describe("Action display label, e.g. 'Create Account'"),
|
|
884
888
|
description: z.string().min(1).max(5000).describe("What this action does — the LLM uses this to decide when to invoke it. Be specific, e.g. 'Creates a new Account record in Salesforce with the given company name, industry, phone, and website'"),
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
889
|
+
invocationTargetType: z.enum(["Flow", "ApexClass"]).default("Flow").describe("Type of backend to invoke: 'Flow' for AutoLaunchedFlow (most common), 'ApexClass' for an Apex class implementing InvocableMethod."),
|
|
890
|
+
flowApiName: z.string().max(255).optional().describe("API name of the AutoLaunchedFlow to invoke (required when invocationTargetType is Flow). Must be Active with runInMode=SystemModeWithoutSharing."),
|
|
891
|
+
apexClassName: z.string().max(255).optional().describe("API name of the Apex class to invoke (required when invocationTargetType is ApexClass). Class must have an @InvocableMethod."),
|
|
892
|
+
inputs: z.array(AgentParamSchema).optional().describe("Input parameters. Each becomes a parameter the LLM collects from the user. Type MUST match the flow variable / Apex param type."),
|
|
893
|
+
outputs: z.array(AgentParamSchema.omit({ required: true })).optional().describe("Output parameters returned to the agent after execution, e.g. the created record ID"),
|
|
888
894
|
}).strict();
|
|
889
895
|
export const CreateAgentPlannerSchema = z.object({
|
|
890
896
|
plannerName: z.string().min(1).max(80).regex(/^[A-Za-z][A-Za-z0-9_]*$/).describe("Planner API name, e.g. 'CRM_Creator_Planner'. NOTE: Names are permanently reserved even after deletion — use a unique name."),
|
|
891
897
|
label: z.string().min(1).max(255).describe("Planner display label, e.g. 'CRM Creator Planner'"),
|
|
892
898
|
description: z.string().max(1000).optional().describe("Planner description"),
|
|
893
899
|
topicNames: z.array(z.string().min(1).max(80)).min(1).describe("Topic API names (GenAiPlugin developer names) to include, e.g. ['Create_Account_Topic', 'Create_Lead_Topic']. Topics must already exist."),
|
|
900
|
+
dataLibraryName: z.string().max(80).optional().describe("API name of a Data Library (Knowledge Base) to attach to this planner for Knowledge-grounded agents."),
|
|
901
|
+
}).strict();
|
|
902
|
+
export const GetAgentSchema = z.object({
|
|
903
|
+
agentApiName: z.string().min(1).max(80).describe("Agent API name, e.g. 'CRM_Record_Creator'"),
|
|
904
|
+
}).strict();
|
|
905
|
+
export const UpdateAgentTopicSchema = z.object({
|
|
906
|
+
agentApiName: z.string().min(1).max(80).describe("API name of the agent that owns this topic. The agent is automatically deactivated before the update and reactivated after."),
|
|
907
|
+
topicName: z.string().min(1).max(80).regex(/^[A-Za-z][A-Za-z0-9_]*$/).describe("Topic API name to update (GenAiPlugin developer name)"),
|
|
908
|
+
label: z.string().min(1).max(255).describe("Updated topic display label"),
|
|
909
|
+
description: z.string().min(1).max(5000).describe("Updated routing description"),
|
|
910
|
+
scope: z.string().min(1).max(5000).describe("Updated scope"),
|
|
911
|
+
instructions: z.array(z.string().max(2000)).optional().describe("Updated step-by-step instructions (replaces existing)"),
|
|
912
|
+
actions: z.array(z.string().max(80)).optional().describe("Updated action API names (replaces existing)"),
|
|
913
|
+
escalationEnabled: z.boolean().optional().describe("Whether this topic can escalate to a human agent"),
|
|
914
|
+
fallbackTopic: z.string().max(80).optional().describe("API name of fallback topic"),
|
|
915
|
+
}).strict();
|
|
916
|
+
export const UpdateAgentActionSchema = z.object({
|
|
917
|
+
actionName: z.string().min(1).max(80).regex(/^[A-Za-z][A-Za-z0-9_]*$/).describe("Action API name to update (GenAiFunction developer name)"),
|
|
918
|
+
label: z.string().min(1).max(255).describe("Updated action display label"),
|
|
919
|
+
description: z.string().min(1).max(5000).describe("Updated action description"),
|
|
920
|
+
invocationTargetType: z.enum(["Flow", "ApexClass"]).default("Flow").describe("Type of backend to invoke"),
|
|
921
|
+
flowApiName: z.string().max(255).optional().describe("API name of the AutoLaunchedFlow (required if invocationTargetType is Flow)"),
|
|
922
|
+
apexClassName: z.string().max(255).optional().describe("API name of the Apex class (required if invocationTargetType is ApexClass)"),
|
|
923
|
+
inputs: z.array(AgentParamSchema).optional().describe("Updated input parameters (replaces existing)"),
|
|
924
|
+
outputs: z.array(AgentParamSchema.omit({ required: true })).optional().describe("Updated output parameters (replaces existing)"),
|
|
925
|
+
}).strict();
|
|
926
|
+
export const DeleteAgentSchema = z.object({
|
|
927
|
+
agentApiName: z.string().min(1).max(80).describe("API name of the agent to delete, e.g. 'CRM_Record_Creator'"),
|
|
928
|
+
deleteTopics: z.boolean().default(false).describe("If true, also deletes the agent's GenAiPlugin topics (identified via its GenAiPlannerBundle)"),
|
|
929
|
+
deleteActions: z.boolean().default(false).describe("If true, also deletes the GenAiFunction actions referenced by the agent's topics"),
|
|
930
|
+
}).strict();
|
|
931
|
+
export const ListAgentsSchema = z.object({}).strict();
|
|
932
|
+
export const TestAgentSchema = z.object({
|
|
933
|
+
agentApiName: z.string().min(1).max(80).describe("API name of the agent to test, e.g. 'CRM_Record_Creator'"),
|
|
934
|
+
message: z.string().min(1).max(5000).describe("Test message to send to the agent, e.g. 'Create an account called Acme Corp'"),
|
|
935
|
+
orgUrl: z.string().url().optional().describe("Override the Agentforce bootstrap URL (defaults to SF_INSTANCE_URL). Required only if your My Domain URL differs from the instance URL."),
|
|
936
|
+
}).strict();
|
|
937
|
+
export const GetAgentLogsSchema = z.object({
|
|
938
|
+
agentApiName: z.string().max(80).optional().describe("Filter logs by agent API name (optional — omit to see all agent activity in the org)"),
|
|
939
|
+
limit: z.number().int().min(1).max(200).default(20).describe("Maximum number of log entries to return (default: 20)"),
|
|
940
|
+
hoursBack: z.number().min(0.1).max(24).default(1).describe("How many hours back to look (default: 1 hour)"),
|
|
894
941
|
}).strict();
|
|
895
942
|
// ─── MCP SERVER MANAGEMENT ───────────────────────────────────────────────────
|
|
896
943
|
export const CreateMcpServerSchema = z.object({
|
|
@@ -86,11 +86,14 @@ export async function buildGenAiActionZip(params, apiVersion) {
|
|
|
86
86
|
<parameterName>${escapeXml(out.name)}</parameterName>
|
|
87
87
|
<parameterType>output</parameterType>
|
|
88
88
|
</mappingAttributes>`).join("\n");
|
|
89
|
+
const isApex = params.invocationTargetType === "ApexClass";
|
|
90
|
+
const invocationTarget = isApex ? (params.apexClassName ?? "") : (params.flowApiName ?? "");
|
|
91
|
+
const invocationTargetType = isApex ? "apexClass" : "flow";
|
|
89
92
|
const functionXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
90
93
|
<GenAiFunction xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
91
94
|
<description>${escapeXml(params.description)}</description>
|
|
92
|
-
<invocationTarget>${escapeXml(
|
|
93
|
-
<invocationTargetType
|
|
95
|
+
<invocationTarget>${escapeXml(invocationTarget)}</invocationTarget>
|
|
96
|
+
<invocationTargetType>${invocationTargetType}</invocationTargetType>
|
|
94
97
|
<isConfirmationRequired>false</isConfirmationRequired>
|
|
95
98
|
<masterLabel>${escapeXml(params.label)}</masterLabel>
|
|
96
99
|
${inputsXml}
|
|
@@ -1219,6 +1219,8 @@ function buildBotXml(params) {
|
|
|
1219
1219
|
<met:richContentEnabled>true</met:richContentEnabled>
|
|
1220
1220
|
<met:sessionTimeout>0</met:sessionTimeout>
|
|
1221
1221
|
<met:type>${x(params.type ?? "EinsteinCopilot")}</met:type>
|
|
1222
|
+
${params.systemPrompt ? `<met:systemPrompt>${x(params.systemPrompt)}</met:systemPrompt>` : ""}
|
|
1223
|
+
${params.openingMessage ? `<met:welcomeMessage>${x(params.openingMessage)}</met:welcomeMessage>` : ""}
|
|
1222
1224
|
</met:metadata>`;
|
|
1223
1225
|
}
|
|
1224
1226
|
function buildGenAiPluginXml(params) {
|
|
@@ -1239,8 +1241,9 @@ function buildGenAiPluginXml(params) {
|
|
|
1239
1241
|
<met:description>${x(params.description)}</met:description>
|
|
1240
1242
|
<met:scope>${x(params.scope)}</met:scope>
|
|
1241
1243
|
<met:pluginType>Topic</met:pluginType>
|
|
1242
|
-
<met:canEscalate
|
|
1244
|
+
<met:canEscalate>${params.escalationEnabled === true ? "true" : "false"}</met:canEscalate>
|
|
1243
1245
|
<met:language>en_US</met:language>
|
|
1246
|
+
${params.fallbackTopic ? `<met:fallbackPlugin>${x(params.fallbackTopic)}</met:fallbackPlugin>` : ""}
|
|
1244
1247
|
${instructionsXml}
|
|
1245
1248
|
${functionsXml}
|
|
1246
1249
|
</met:metadata>`;
|
|
@@ -1250,11 +1253,16 @@ function buildGenAiPlannerBundleXml(params) {
|
|
|
1250
1253
|
<met:genAiPlugins>
|
|
1251
1254
|
<met:genAiPluginName>${x(t)}</met:genAiPluginName>
|
|
1252
1255
|
</met:genAiPlugins>`).join("\n");
|
|
1256
|
+
const dataLibXml = params.dataLibraryName ? `
|
|
1257
|
+
<met:dataLibraries>
|
|
1258
|
+
<met:dataLibraryName>${x(params.dataLibraryName)}</met:dataLibraryName>
|
|
1259
|
+
</met:dataLibraries>` : "";
|
|
1253
1260
|
return `<met:metadata xsi:type="met:GenAiPlannerBundle" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1254
1261
|
<met:fullName>${x(params.plannerName)}</met:fullName>
|
|
1255
1262
|
<met:masterLabel>${x(params.label)}</met:masterLabel>
|
|
1256
1263
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1257
1264
|
<met:plannerType>AiCopilot__ReAct</met:plannerType>
|
|
1265
|
+
${dataLibXml}
|
|
1258
1266
|
${pluginsXml}
|
|
1259
1267
|
</met:metadata>`;
|
|
1260
1268
|
}
|
|
@@ -1527,4 +1535,328 @@ export async function deactivateAgent(auth, agentApiName) {
|
|
|
1527
1535
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1528
1536
|
}
|
|
1529
1537
|
}
|
|
1538
|
+
// ─── Metadata read/list/delete helpers ───────────────────────────────────────
|
|
1539
|
+
export async function readMetadataItem(auth, type, fullName) {
|
|
1540
|
+
const body = `<met:readMetadata>
|
|
1541
|
+
<met:type>${type}</met:type>
|
|
1542
|
+
<met:fullNames>${x(fullName)}</met:fullNames>
|
|
1543
|
+
</met:readMetadata>`;
|
|
1544
|
+
try {
|
|
1545
|
+
const xml = await callMetadataSoap(auth, "readMetadata", body);
|
|
1546
|
+
const error = extractSoapError(xml);
|
|
1547
|
+
if (error)
|
|
1548
|
+
return { success: false, message: error };
|
|
1549
|
+
return { success: true, rawXml: xml };
|
|
1550
|
+
}
|
|
1551
|
+
catch (err) {
|
|
1552
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
export async function listMetadataType(auth, type) {
|
|
1556
|
+
const body = `<met:listMetadata>
|
|
1557
|
+
<met:queries><met:type>${x(type)}</met:type></met:queries>
|
|
1558
|
+
<met:asOfVersion>${API_VERSION}</met:asOfVersion>
|
|
1559
|
+
</met:listMetadata>`;
|
|
1560
|
+
try {
|
|
1561
|
+
const xml = await callMetadataSoap(auth, "listMetadata", body);
|
|
1562
|
+
const error = extractSoapError(xml);
|
|
1563
|
+
if (error)
|
|
1564
|
+
return { success: false, message: error, items: [] };
|
|
1565
|
+
const items = [];
|
|
1566
|
+
for (const block of [...xml.matchAll(/<result>([\s\S]*?)<\/result>/gi)]) {
|
|
1567
|
+
const inner = block[1];
|
|
1568
|
+
const fullNameMatch = inner.match(/<fullName[^>]*>([\s\S]*?)<\/fullName>/i);
|
|
1569
|
+
if (fullNameMatch) {
|
|
1570
|
+
const labelMatch = inner.match(/<label[^>]*>([\s\S]*?)<\/label>/i);
|
|
1571
|
+
const lastModifiedMatch = inner.match(/<lastModifiedDate[^>]*>([\s\S]*?)<\/lastModifiedDate>/i);
|
|
1572
|
+
items.push({
|
|
1573
|
+
fullName: fullNameMatch[1].trim(),
|
|
1574
|
+
label: labelMatch?.[1].trim() ?? "",
|
|
1575
|
+
lastModifiedDate: lastModifiedMatch?.[1].trim() ?? "",
|
|
1576
|
+
type,
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
return { success: true, items };
|
|
1581
|
+
}
|
|
1582
|
+
catch (err) {
|
|
1583
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)), items: [] };
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
export async function deleteMetadataItems(auth, type, fullNames) {
|
|
1587
|
+
if (fullNames.length === 0)
|
|
1588
|
+
return { success: true, deleted: [], message: "Nothing to delete" };
|
|
1589
|
+
const fullNamesXml = fullNames.map(n => `<met:fullNames>${x(n)}</met:fullNames>`).join("\n");
|
|
1590
|
+
const body = `<met:deleteMetadata>
|
|
1591
|
+
<met:type>${x(type)}</met:type>
|
|
1592
|
+
${fullNamesXml}
|
|
1593
|
+
</met:deleteMetadata>`;
|
|
1594
|
+
try {
|
|
1595
|
+
const xml = await callMetadataSoap(auth, "deleteMetadata", body);
|
|
1596
|
+
const error = extractSoapError(xml);
|
|
1597
|
+
if (error)
|
|
1598
|
+
return { success: false, message: error, deleted: [] };
|
|
1599
|
+
const deleted = [];
|
|
1600
|
+
const errors = [];
|
|
1601
|
+
for (const block of [...xml.matchAll(/<result>([\s\S]*?)<\/result>/gi)]) {
|
|
1602
|
+
const inner = block[1];
|
|
1603
|
+
const fullNameMatch = inner.match(/<fullName[^>]*>([\s\S]*?)<\/fullName>/i);
|
|
1604
|
+
const successMatch = inner.match(/<success>(true|false)<\/success>/i);
|
|
1605
|
+
const fnName = fullNameMatch?.[1].trim() ?? "";
|
|
1606
|
+
if (successMatch?.[1] === "true") {
|
|
1607
|
+
deleted.push(fnName);
|
|
1608
|
+
}
|
|
1609
|
+
else {
|
|
1610
|
+
const msgMatch = inner.match(/<message[^>]*>([\s\S]*?)<\/message>/i);
|
|
1611
|
+
errors.push(`${fnName}: ${msgMatch?.[1].trim() ?? "Unknown error"}`);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
return {
|
|
1615
|
+
success: errors.length === 0,
|
|
1616
|
+
deleted,
|
|
1617
|
+
message: deleted.length > 0 ? `Deleted: ${deleted.join(", ")}` : "Nothing deleted",
|
|
1618
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
catch (err) {
|
|
1622
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)), deleted: [] };
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
// ─── Agentforce query/management functions ────────────────────────────────────
|
|
1626
|
+
export async function getAgent(auth, agentApiName) {
|
|
1627
|
+
try {
|
|
1628
|
+
let status = "Unknown";
|
|
1629
|
+
const statusResp = await fetchWithTimeout(`${auth.instanceUrl}/services/data/v${API_VERSION}/connect/einstein/copilot/${agentApiName}`, { method: "GET", headers: { "Authorization": `Bearer ${auth.accessToken}`, "Content-Type": "application/json" } }, 15_000).catch(() => null);
|
|
1630
|
+
if (statusResp?.ok) {
|
|
1631
|
+
const data = await statusResp.json().catch(() => ({}));
|
|
1632
|
+
status = data.status ?? data.botStatus ?? "Unknown";
|
|
1633
|
+
}
|
|
1634
|
+
const [botResult, pluginList, funcList, plannerList] = await Promise.all([
|
|
1635
|
+
readMetadataItem(auth, "Bot", agentApiName),
|
|
1636
|
+
listMetadataType(auth, "GenAiPlugin"),
|
|
1637
|
+
listMetadataType(auth, "GenAiFunction"),
|
|
1638
|
+
listMetadataType(auth, "GenAiPlannerBundle"),
|
|
1639
|
+
]);
|
|
1640
|
+
return {
|
|
1641
|
+
success: true,
|
|
1642
|
+
agent: { apiName: agentApiName, status, metadataFound: botResult.success },
|
|
1643
|
+
topics: pluginList.items,
|
|
1644
|
+
actions: funcList.items,
|
|
1645
|
+
planners: plannerList.items,
|
|
1646
|
+
message: `Agent '${agentApiName}' — status: ${status}, ${pluginList.items.length} topic(s), ${funcList.items.length} action(s), ${plannerList.items.length} planner(s).`,
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
catch (err) {
|
|
1650
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
export async function listAgents(auth) {
|
|
1654
|
+
try {
|
|
1655
|
+
const botList = await listMetadataType(auth, "Bot");
|
|
1656
|
+
if (!botList.success)
|
|
1657
|
+
return { success: false, message: botList.message ?? "Failed to list agents", agents: [] };
|
|
1658
|
+
const agents = [];
|
|
1659
|
+
for (const bot of botList.items) {
|
|
1660
|
+
let status = "Unknown";
|
|
1661
|
+
const resp = await fetchWithTimeout(`${auth.instanceUrl}/services/data/v${API_VERSION}/connect/einstein/copilot/${bot.fullName}`, { method: "GET", headers: { "Authorization": `Bearer ${auth.accessToken}` } }, 10_000).catch(() => null);
|
|
1662
|
+
if (resp?.ok) {
|
|
1663
|
+
const data = await resp.json().catch(() => ({}));
|
|
1664
|
+
status = data.status ?? data.botStatus ?? "Unknown";
|
|
1665
|
+
}
|
|
1666
|
+
agents.push({ apiName: bot.fullName, label: bot.label, status, lastModifiedDate: bot.lastModifiedDate });
|
|
1667
|
+
}
|
|
1668
|
+
return { success: true, agents, message: `Found ${agents.length} agent(s).` };
|
|
1669
|
+
}
|
|
1670
|
+
catch (err) {
|
|
1671
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)), agents: [] };
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
export async function updateAgentTopic(auth, params, agentApiName) {
|
|
1675
|
+
try {
|
|
1676
|
+
if (agentApiName) {
|
|
1677
|
+
const deactResult = await deactivateAgent(auth, agentApiName);
|
|
1678
|
+
if (!deactResult.success)
|
|
1679
|
+
console.error(`Warning: deactivation returned: ${deactResult.message}`);
|
|
1680
|
+
}
|
|
1681
|
+
const result = await upsertMetadata(auth, buildGenAiPluginXml(params));
|
|
1682
|
+
if (agentApiName) {
|
|
1683
|
+
const actResult = await activateAgent(auth, agentApiName);
|
|
1684
|
+
if (!actResult.success)
|
|
1685
|
+
return { ...result, warning: `Topic updated but agent reactivation failed: ${actResult.message}` };
|
|
1686
|
+
}
|
|
1687
|
+
return result;
|
|
1688
|
+
}
|
|
1689
|
+
catch (err) {
|
|
1690
|
+
if (agentApiName)
|
|
1691
|
+
await activateAgent(auth, agentApiName).catch(() => { });
|
|
1692
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
export async function deleteAgent(auth, params) {
|
|
1696
|
+
const deleted = [];
|
|
1697
|
+
const errors = [];
|
|
1698
|
+
try {
|
|
1699
|
+
await deactivateAgent(auth, params.agentApiName).catch(() => { });
|
|
1700
|
+
const botRead = await readMetadataItem(auth, "Bot", params.agentApiName);
|
|
1701
|
+
let topicsToDelete = [];
|
|
1702
|
+
let actionsToDelete = [];
|
|
1703
|
+
let plannerToDelete = null;
|
|
1704
|
+
if (botRead.success && botRead.rawXml) {
|
|
1705
|
+
const plannerMatch = botRead.rawXml.match(/<plannerBundle[^>]*>([\s\S]*?)<\/plannerBundle>/i)
|
|
1706
|
+
|| botRead.rawXml.match(/<plannerBundleName[^>]*>([\s\S]*?)<\/plannerBundleName>/i);
|
|
1707
|
+
if (plannerMatch)
|
|
1708
|
+
plannerToDelete = plannerMatch[1].trim();
|
|
1709
|
+
}
|
|
1710
|
+
if ((params.deleteTopics || params.deleteActions) && plannerToDelete) {
|
|
1711
|
+
const plannerRead = await readMetadataItem(auth, "GenAiPlannerBundle", plannerToDelete);
|
|
1712
|
+
if (plannerRead.success && plannerRead.rawXml) {
|
|
1713
|
+
topicsToDelete = [...plannerRead.rawXml.matchAll(/<genAiPluginName[^>]*>([\s\S]*?)<\/genAiPluginName>/gi)].map(m => m[1].trim());
|
|
1714
|
+
if (params.deleteActions) {
|
|
1715
|
+
for (const topicName of topicsToDelete) {
|
|
1716
|
+
const topicRead = await readMetadataItem(auth, "GenAiPlugin", topicName);
|
|
1717
|
+
if (topicRead.success && topicRead.rawXml) {
|
|
1718
|
+
const actionNames = [...topicRead.rawXml.matchAll(/<functionName[^>]*>([\s\S]*?)<\/functionName>/gi)].map(m => m[1].trim());
|
|
1719
|
+
actionsToDelete.push(...actionNames);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
actionsToDelete = [...new Set(actionsToDelete)];
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
if (actionsToDelete.length > 0) {
|
|
1727
|
+
const res = await deleteMetadataItems(auth, "GenAiFunction", actionsToDelete);
|
|
1728
|
+
deleted.push(...(res.deleted ?? []));
|
|
1729
|
+
if (res.errors)
|
|
1730
|
+
errors.push(...res.errors);
|
|
1731
|
+
}
|
|
1732
|
+
if (topicsToDelete.length > 0) {
|
|
1733
|
+
const res = await deleteMetadataItems(auth, "GenAiPlugin", topicsToDelete);
|
|
1734
|
+
deleted.push(...(res.deleted ?? []));
|
|
1735
|
+
if (res.errors)
|
|
1736
|
+
errors.push(...res.errors);
|
|
1737
|
+
}
|
|
1738
|
+
if (plannerToDelete) {
|
|
1739
|
+
const res = await deleteMetadataItems(auth, "GenAiPlannerBundle", [plannerToDelete]);
|
|
1740
|
+
deleted.push(...(res.deleted ?? []));
|
|
1741
|
+
if (res.errors)
|
|
1742
|
+
errors.push(...res.errors);
|
|
1743
|
+
}
|
|
1744
|
+
const botRes = await deleteMetadataItems(auth, "Bot", [params.agentApiName]);
|
|
1745
|
+
deleted.push(...(botRes.deleted ?? []));
|
|
1746
|
+
if (botRes.errors)
|
|
1747
|
+
errors.push(...botRes.errors);
|
|
1748
|
+
return {
|
|
1749
|
+
success: errors.length === 0,
|
|
1750
|
+
deleted,
|
|
1751
|
+
message: `Deleted ${deleted.length} component(s)${deleted.length > 0 ? ": " + deleted.join(", ") : ""}.`,
|
|
1752
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
catch (err) {
|
|
1756
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)), deleted };
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
export async function testAgent(auth, params) {
|
|
1760
|
+
try {
|
|
1761
|
+
const orgUrl = (params.orgUrl ?? auth.instanceUrl).replace(/\/$/, "");
|
|
1762
|
+
const externalSessionKey = `mcp_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
1763
|
+
const bootstrapResp = await fetchWithTimeout(`${orgUrl}/agentforce/bootstrap/nameduser`, {
|
|
1764
|
+
method: "POST",
|
|
1765
|
+
headers: { "Authorization": `Bearer ${auth.accessToken}`, "Content-Type": "application/json" },
|
|
1766
|
+
body: JSON.stringify({ botApiName: params.agentApiName, externalSessionKey, forceConfig: { endpoint: orgUrl } }),
|
|
1767
|
+
}, 30_000);
|
|
1768
|
+
if (!bootstrapResp.ok) {
|
|
1769
|
+
const body = await bootstrapResp.text().catch(() => "");
|
|
1770
|
+
return { success: false, message: `Bootstrap failed (HTTP ${bootstrapResp.status}): ${body.slice(0, 400)}` };
|
|
1771
|
+
}
|
|
1772
|
+
const bootstrap = await bootstrapResp.json();
|
|
1773
|
+
const agentToken = bootstrap.token;
|
|
1774
|
+
const agentUrl = (bootstrap.agentUrl ?? "").replace(/\/$/, "");
|
|
1775
|
+
if (!agentToken || !agentUrl)
|
|
1776
|
+
return { success: false, message: `Bootstrap missing token or agentUrl. Response: ${JSON.stringify(bootstrap).slice(0, 300)}` };
|
|
1777
|
+
const sessionResp = await fetchWithTimeout(`${agentUrl}/sessions`, {
|
|
1778
|
+
method: "POST",
|
|
1779
|
+
headers: { "Authorization": `Bearer ${agentToken}`, "Content-Type": "application/json", "x-sfdc-app-context": "EinsteinGPT", "x-client-feature-id": "ai-platform-einstein-gpt" },
|
|
1780
|
+
body: JSON.stringify({ externalSessionKey, instanceConfig: { endpoint: orgUrl } }),
|
|
1781
|
+
}, 30_000);
|
|
1782
|
+
if (!sessionResp.ok) {
|
|
1783
|
+
const body = await sessionResp.text().catch(() => "");
|
|
1784
|
+
return { success: false, message: `Session creation failed (HTTP ${sessionResp.status}): ${body.slice(0, 400)}` };
|
|
1785
|
+
}
|
|
1786
|
+
const sessionData = await sessionResp.json();
|
|
1787
|
+
const sessionId = sessionData.sessionId ?? sessionData.id;
|
|
1788
|
+
if (!sessionId)
|
|
1789
|
+
return { success: false, message: `No sessionId in response: ${JSON.stringify(sessionData).slice(0, 300)}` };
|
|
1790
|
+
const msgResp = await fetchWithTimeout(`${agentUrl}/sessions/${sessionId}/messages`, {
|
|
1791
|
+
method: "POST",
|
|
1792
|
+
headers: { "Authorization": `Bearer ${agentToken}`, "Content-Type": "application/json", "x-sfdc-app-context": "EinsteinGPT", "x-client-feature-id": "ai-platform-einstein-gpt" },
|
|
1793
|
+
body: JSON.stringify({ message: { role: "user", content: [{ type: "text", text: params.message }] }, variables: [] }),
|
|
1794
|
+
}, 90_000);
|
|
1795
|
+
if (!msgResp.ok) {
|
|
1796
|
+
const body = await msgResp.text().catch(() => "");
|
|
1797
|
+
return { success: false, sessionId, message: `Message failed (HTTP ${msgResp.status}): ${body.slice(0, 400)}` };
|
|
1798
|
+
}
|
|
1799
|
+
const msgData = await msgResp.json();
|
|
1800
|
+
const messages = msgData.messages ?? msgData.result?.messages ?? [];
|
|
1801
|
+
const responseText = messages
|
|
1802
|
+
.filter(m => m.type === "Reply" || m.role === "agent")
|
|
1803
|
+
.map(m => {
|
|
1804
|
+
if (typeof m.message === "string")
|
|
1805
|
+
return m.message;
|
|
1806
|
+
if (Array.isArray(m.content))
|
|
1807
|
+
return m.content.map(c => c.text ?? "").join("");
|
|
1808
|
+
return m.text ?? "";
|
|
1809
|
+
})
|
|
1810
|
+
.filter(Boolean)
|
|
1811
|
+
.join("\n")
|
|
1812
|
+
.trim();
|
|
1813
|
+
return {
|
|
1814
|
+
success: true,
|
|
1815
|
+
sessionId,
|
|
1816
|
+
response: responseText || JSON.stringify(msgData).slice(0, 1000),
|
|
1817
|
+
message: responseText ? "Agent responded successfully." : "Message sent successfully.",
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
catch (err) {
|
|
1821
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
export async function getAgentLogs(auth, params) {
|
|
1825
|
+
try {
|
|
1826
|
+
const client = createClient(auth);
|
|
1827
|
+
const limit = params.limit ?? 20;
|
|
1828
|
+
let whereClause = "LogDate = TODAY";
|
|
1829
|
+
if (params.agentApiName) {
|
|
1830
|
+
const safe = params.agentApiName.replace(/'/g, "\\'");
|
|
1831
|
+
whereClause += ` AND (EventTarget LIKE '%${safe}%' OR EventDetails LIKE '%${safe}%')`;
|
|
1832
|
+
}
|
|
1833
|
+
const query = `SELECT EventLabel, EventTarget, EventDetails, EventDate FROM ConversationDefinitionEventLog WHERE ${whereClause} ORDER BY EventDate DESC LIMIT ${limit}`;
|
|
1834
|
+
const resp = await client.get(`/query?q=${encodeURIComponent(query)}`);
|
|
1835
|
+
const records = resp.data.records ?? [];
|
|
1836
|
+
const topicEvents = records.filter(r => (r.EventLabel ?? "").includes("TopicClassification")).length;
|
|
1837
|
+
const actionEvents = records.filter(r => (r.EventLabel ?? "").includes("ActionExecuted")).length;
|
|
1838
|
+
const errorEvents = records.filter(r => (r.EventLabel ?? "").toLowerCase().includes("error") || (r.EventLabel ?? "").toLowerCase().includes("fail")).length;
|
|
1839
|
+
const logs = records.map(r => {
|
|
1840
|
+
let details = r.EventDetails;
|
|
1841
|
+
try {
|
|
1842
|
+
details = JSON.parse(r.EventDetails ?? "{}");
|
|
1843
|
+
}
|
|
1844
|
+
catch { /* keep raw */ }
|
|
1845
|
+
return { time: r.EventDate, event: r.EventLabel, target: r.EventTarget, details };
|
|
1846
|
+
});
|
|
1847
|
+
return {
|
|
1848
|
+
success: true,
|
|
1849
|
+
totalRecords: records.length,
|
|
1850
|
+
summary: { topicClassifications: topicEvents, actionsExecuted: actionEvents, errors: errorEvents },
|
|
1851
|
+
logs,
|
|
1852
|
+
message: `${records.length} entries: ${topicEvents} topic classification(s), ${actionEvents} action execution(s), ${errorEvents} error(s).`,
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
catch (err) {
|
|
1856
|
+
const msg = sanitizeError(err instanceof Error ? err.message : String(err));
|
|
1857
|
+
if (msg.includes("INVALID_TYPE") || msg.includes("ConversationDefinition"))
|
|
1858
|
+
return { success: false, message: "ConversationDefinitionEventLog is not available in this org. Requires Event Monitoring or Agentforce debugging enabled.", logs: [] };
|
|
1859
|
+
return { success: false, message: msg, logs: [] };
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1530
1862
|
//# sourceMappingURL=salesforce.js.map
|
package/dist/tools/agentforce.js
CHANGED
|
@@ -1,66 +1,75 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
CreateAgentSchema, CreateAgentTopicSchema, CreateAgentActionSchema, CreateAgentPlannerSchema,
|
|
3
|
+
GetAgentSchema, UpdateAgentTopicSchema, UpdateAgentActionSchema, DeleteAgentSchema,
|
|
4
|
+
ListAgentsSchema, TestAgentSchema, GetAgentLogsSchema,
|
|
5
|
+
} from "../schemas/index.js";
|
|
6
|
+
import {
|
|
7
|
+
getAuth, createAgent, createAgentTopic, createAgentPlanner,
|
|
8
|
+
activateAgent, deactivateAgent, getAgent, listAgents,
|
|
9
|
+
updateAgentTopic, deleteAgent, testAgent, getAgentLogs, API_VERSION,
|
|
10
|
+
} from "../services/salesforce.js";
|
|
3
11
|
import { buildGenAiActionZip, deployZip, pollDeployStatus } from "../services/deployment.js";
|
|
4
12
|
import { resultContent } from "./utils.js";
|
|
13
|
+
|
|
5
14
|
export function registerAgentforceTools(server) {
|
|
15
|
+
// ─── CREATE ──────────────────────────────────────────────────────────────
|
|
6
16
|
server.registerTool("sf_create_agent", {
|
|
7
17
|
title: "Create Agentforce Agent",
|
|
8
18
|
description: `Creates an Agentforce Agent (Einstein Copilot / Service Agent) in Salesforce using the Bot metadata type.
|
|
9
19
|
|
|
10
20
|
IMPORTANT: Follow this exact creation order for a fully working agent:
|
|
11
|
-
1. Create flows (sf_create_flow) —
|
|
12
|
-
2. Create agent actions (sf_create_agent_action) — link each flow as a GenAiFunction
|
|
21
|
+
1. Create flows (sf_create_flow) — AutoLaunchedFlows that perform actions, must be Active with runInMode=SystemModeWithoutSharing
|
|
22
|
+
2. Create agent actions (sf_create_agent_action) — link each flow as a GenAiFunction with LLM-facing schemas
|
|
13
23
|
3. Create agent topics (sf_create_agent_topic) — create GenAiPlugin topics referencing the actions
|
|
14
|
-
4. Create agent planner (sf_create_agent_planner) — create GenAiPlannerBundle linking all topics
|
|
24
|
+
4. Create agent planner (sf_create_agent_planner) — create GenAiPlannerBundle linking all topics (CRITICAL link)
|
|
15
25
|
5. Create agent (sf_create_agent) — create the Bot referencing the planner
|
|
16
26
|
6. Activate the agent (sf_activate_agent)
|
|
17
27
|
|
|
18
|
-
The agent type is IMMUTABLE after creation — choose carefully
|
|
28
|
+
The agent type is IMMUTABLE after creation — choose carefully:
|
|
29
|
+
- EinsteinCopilot: internal employee-facing (Lightning sidebar)
|
|
30
|
+
- ExternalCopilot: customer-facing Agentforce Service Agent`,
|
|
19
31
|
inputSchema: CreateAgentSchema,
|
|
20
32
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
21
33
|
}, async (params) => {
|
|
22
34
|
const auth = await getAuth();
|
|
23
|
-
const result = await createAgent(auth,
|
|
24
|
-
agentName: params.agentName,
|
|
25
|
-
label: params.label,
|
|
26
|
-
description: params.description,
|
|
27
|
-
type: params.type,
|
|
28
|
-
});
|
|
35
|
+
const result = await createAgent(auth, params);
|
|
29
36
|
return resultContent(result);
|
|
30
37
|
});
|
|
38
|
+
|
|
31
39
|
server.registerTool("sf_create_agent_topic", {
|
|
32
40
|
title: "Create Agentforce Topic (GenAiPlugin)",
|
|
33
|
-
description: `Creates a Topic (GenAiPlugin) for an Agentforce Agent. Topics define the agent's areas of expertise — what types of user requests it handles. Each topic has a
|
|
41
|
+
description: `Creates a Topic (GenAiPlugin) for an Agentforce Agent. Topics define the agent's areas of expertise — what types of user requests it handles. Each topic has a routing description, scope, step-by-step instructions, and references to agent actions.
|
|
34
42
|
|
|
35
43
|
Create topics AFTER creating agent actions (sf_create_agent_action) since topics reference actions by API name.
|
|
36
|
-
Then create the planner bundle (sf_create_agent_planner) to link all topics to the agent
|
|
44
|
+
Then create the planner bundle (sf_create_agent_planner) to link all topics to the agent.
|
|
45
|
+
|
|
46
|
+
Set escalationEnabled: true to allow this topic to escalate to a human agent.
|
|
47
|
+
Set fallbackTopic to route to another topic when this one cannot handle the request.`,
|
|
37
48
|
inputSchema: CreateAgentTopicSchema,
|
|
38
49
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
39
50
|
}, async (params) => {
|
|
40
51
|
const auth = await getAuth();
|
|
41
|
-
const result = await createAgentTopic(auth,
|
|
42
|
-
topicName: params.topicName,
|
|
43
|
-
label: params.label,
|
|
44
|
-
description: params.description,
|
|
45
|
-
scope: params.scope,
|
|
46
|
-
instructions: params.instructions,
|
|
47
|
-
actions: params.actions,
|
|
48
|
-
});
|
|
52
|
+
const result = await createAgentTopic(auth, params);
|
|
49
53
|
return resultContent(result);
|
|
50
54
|
});
|
|
55
|
+
|
|
51
56
|
server.registerTool("sf_create_agent_action", {
|
|
52
57
|
title: "Create Agentforce Action (GenAiFunction)",
|
|
53
|
-
description: `Creates an Agentforce Action (GenAiFunction) linked to a Flow. Actions are the concrete steps an agent takes when handling a topic.
|
|
58
|
+
description: `Creates an Agentforce Action (GenAiFunction) linked to a Flow or Apex class. Actions are the concrete steps an agent takes when handling a topic.
|
|
54
59
|
|
|
55
|
-
IMPORTANT:
|
|
60
|
+
IMPORTANT: Deploys a complete GenAiFunction bundle including:
|
|
56
61
|
- The GenAiFunction metadata (invocationTarget, invocationTargetType, mappingAttributes)
|
|
57
|
-
- input/schema.json — the LLM-facing JSON schema for input parameters (REQUIRED for
|
|
62
|
+
- input/schema.json — the LLM-facing JSON schema for input parameters (REQUIRED for invocation)
|
|
58
63
|
- output/schema.json — the LLM-facing JSON schema for output parameters
|
|
59
64
|
|
|
60
|
-
Without
|
|
65
|
+
Without schema files the LLM cannot invoke the action. This tool generates all three files automatically.
|
|
66
|
+
|
|
67
|
+
Set invocationTargetType to "ApexClass" for Apex-backed actions (provide apexClassName instead of flowApiName).
|
|
68
|
+
For Flow actions (default): the flow must be Active with runInMode=SystemModeWithoutSharing.
|
|
61
69
|
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
Type matching: inputs/outputs type MUST match the flow variable type — use "Currency" for Currency fields, "Date" for Date fields, etc. Using "Text" for a Currency field causes "incorrect format" errors.
|
|
71
|
+
|
|
72
|
+
Create actions BEFORE topics (topics reference actions by API name).`,
|
|
64
73
|
inputSchema: CreateAgentActionSchema,
|
|
65
74
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
66
75
|
}, async (params) => {
|
|
@@ -70,7 +79,9 @@ The referenced flow MUST exist and be Active with runInMode=SystemModeWithoutSha
|
|
|
70
79
|
actionName: params.actionName,
|
|
71
80
|
label: params.label,
|
|
72
81
|
description: params.description,
|
|
82
|
+
invocationTargetType: params.invocationTargetType,
|
|
73
83
|
flowApiName: params.flowApiName,
|
|
84
|
+
apexClassName: params.apexClassName,
|
|
74
85
|
inputs: params.inputs ?? [],
|
|
75
86
|
outputs: params.outputs ?? [],
|
|
76
87
|
}, API_VERSION);
|
|
@@ -82,6 +93,7 @@ The referenced flow MUST exist and be Active with runInMode=SystemModeWithoutSha
|
|
|
82
93
|
return resultContent({ success: false, message: err instanceof Error ? err.message : String(err) });
|
|
83
94
|
}
|
|
84
95
|
});
|
|
96
|
+
|
|
85
97
|
server.registerTool("sf_create_agent_planner", {
|
|
86
98
|
title: "Create Agentforce Planner Bundle (GenAiPlannerBundle)",
|
|
87
99
|
description: `Creates a GenAiPlannerBundle that connects topics to the agent's planning engine.
|
|
@@ -89,22 +101,20 @@ The referenced flow MUST exist and be Active with runInMode=SystemModeWithoutSha
|
|
|
89
101
|
The planner bundle is the CRITICAL link between the Bot and its topics (GenAiPlugins). Without it, the agent will have 0 topics visible and cannot route requests or invoke actions.
|
|
90
102
|
|
|
91
103
|
Create the planner bundle AFTER creating all topics (sf_create_agent_topic) and BEFORE creating the agent (sf_create_agent).
|
|
92
|
-
Then reference this planner's API name when calling sf_create_agent
|
|
104
|
+
Then reference this planner's API name when calling sf_create_agent.
|
|
105
|
+
|
|
106
|
+
For Knowledge-grounded agents, set dataLibraryName to the API name of a Data Library (Knowledge Base).`,
|
|
93
107
|
inputSchema: CreateAgentPlannerSchema,
|
|
94
108
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
95
109
|
}, async (params) => {
|
|
96
110
|
const auth = await getAuth();
|
|
97
|
-
const result = await createAgentPlanner(auth,
|
|
98
|
-
plannerName: params.plannerName,
|
|
99
|
-
label: params.label,
|
|
100
|
-
description: params.description,
|
|
101
|
-
topicNames: params.topicNames,
|
|
102
|
-
});
|
|
111
|
+
const result = await createAgentPlanner(auth, params);
|
|
103
112
|
return resultContent(result);
|
|
104
113
|
});
|
|
114
|
+
|
|
105
115
|
server.registerTool("sf_activate_agent", {
|
|
106
116
|
title: "Activate Agentforce Agent",
|
|
107
|
-
description: `Activates an Agentforce Agent so it is available to users.
|
|
117
|
+
description: `Activates an Agentforce Agent so it is available to users. After making configuration changes (adding/removing topics or actions), re-activate with this tool.`,
|
|
108
118
|
inputSchema: { type: "object", properties: { agentApiName: { type: "string", description: "API name of the agent to activate, e.g. 'CRM_Record_Creator'" } }, required: ["agentApiName"] },
|
|
109
119
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
110
120
|
}, async (params) => {
|
|
@@ -112,6 +122,7 @@ Then reference this planner's API name when calling sf_create_agent.`,
|
|
|
112
122
|
const result = await activateAgent(auth, params.agentApiName);
|
|
113
123
|
return resultContent(result);
|
|
114
124
|
});
|
|
125
|
+
|
|
115
126
|
server.registerTool("sf_deactivate_agent", {
|
|
116
127
|
title: "Deactivate Agentforce Agent",
|
|
117
128
|
description: `Deactivates an Agentforce Agent so configuration changes can be made. You must deactivate an agent before modifying its topics, actions, or planner configuration. After making changes, re-activate with sf_activate_agent.`,
|
|
@@ -122,5 +133,153 @@ Then reference this planner's API name when calling sf_create_agent.`,
|
|
|
122
133
|
const result = await deactivateAgent(auth, params.agentApiName);
|
|
123
134
|
return resultContent(result);
|
|
124
135
|
});
|
|
136
|
+
|
|
137
|
+
// ─── READ ────────────────────────────────────────────────────────────────
|
|
138
|
+
server.registerTool("sf_get_agent", {
|
|
139
|
+
title: "Get Agentforce Agent Configuration",
|
|
140
|
+
description: `Retrieves the full configuration of an Agentforce Agent including activation status, all GenAiPlugin topics, GenAiFunction actions, and GenAiPlannerBundle planners in the org.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
- agent.status: Active / Inactive / Unknown
|
|
144
|
+
- topics[]: all GenAiPlugin topics (name, label, lastModified)
|
|
145
|
+
- actions[]: all GenAiFunction actions (name, label, lastModified)
|
|
146
|
+
- planners[]: all GenAiPlannerBundle planners (name, label, lastModified)
|
|
147
|
+
|
|
148
|
+
Note: topics/actions/planners list all components in the org (not filtered to this agent). Use sf_get_agent_logs to see which are actively routing.`,
|
|
149
|
+
inputSchema: GetAgentSchema,
|
|
150
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
151
|
+
}, async (params) => {
|
|
152
|
+
const auth = await getAuth();
|
|
153
|
+
const result = await getAgent(auth, params.agentApiName);
|
|
154
|
+
return resultContent(result);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
server.registerTool("sf_list_agents", {
|
|
158
|
+
title: "List All Agentforce Agents",
|
|
159
|
+
description: `Lists all Agentforce Agents (Bots) in the org with their API name, label, activation status, and last modified date.
|
|
160
|
+
|
|
161
|
+
Use this to discover agents before updating, testing, or deleting them.`,
|
|
162
|
+
inputSchema: ListAgentsSchema,
|
|
163
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
164
|
+
}, async (_params) => {
|
|
165
|
+
const auth = await getAuth();
|
|
166
|
+
const result = await listAgents(auth);
|
|
167
|
+
return resultContent(result);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// ─── UPDATE ──────────────────────────────────────────────────────────────
|
|
171
|
+
server.registerTool("sf_update_agent_topic", {
|
|
172
|
+
title: "Update Agentforce Topic (GenAiPlugin)",
|
|
173
|
+
description: `Updates an existing GenAiPlugin topic in-place without deleting and recreating it. Automatically deactivates the agent before the update and reactivates it after.
|
|
174
|
+
|
|
175
|
+
Use this to change a topic's routing description, scope, instructions, or linked actions.
|
|
176
|
+
All fields provided replace the existing values (instructions and actions arrays are replaced entirely).
|
|
177
|
+
|
|
178
|
+
The agent must exist and be accessible — provide agentApiName for automatic deactivate/reactivate.`,
|
|
179
|
+
inputSchema: UpdateAgentTopicSchema,
|
|
180
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
181
|
+
}, async (params) => {
|
|
182
|
+
const auth = await getAuth();
|
|
183
|
+
const { agentApiName, ...topicParams } = params;
|
|
184
|
+
const result = await updateAgentTopic(auth, topicParams, agentApiName);
|
|
185
|
+
return resultContent(result);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
server.registerTool("sf_update_agent_action", {
|
|
189
|
+
title: "Update Agentforce Action (GenAiFunction)",
|
|
190
|
+
description: `Updates an existing GenAiFunction action and regenerates its LLM-facing input/output schema files. Uses zip-based metadata deploy (same as sf_create_agent_action) since GenAiFunction bundles require schema files.
|
|
191
|
+
|
|
192
|
+
Use this to change an action's description, input/output parameters, or switch from one flow to another.
|
|
193
|
+
All fields provided replace the existing values.
|
|
194
|
+
|
|
195
|
+
After updating an action, the topics that reference it will automatically use the new schema on the next agent invocation.`,
|
|
196
|
+
inputSchema: UpdateAgentActionSchema,
|
|
197
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
198
|
+
}, async (params) => {
|
|
199
|
+
const auth = await getAuth();
|
|
200
|
+
try {
|
|
201
|
+
const base64Zip = await buildGenAiActionZip({
|
|
202
|
+
actionName: params.actionName,
|
|
203
|
+
label: params.label,
|
|
204
|
+
description: params.description,
|
|
205
|
+
invocationTargetType: params.invocationTargetType,
|
|
206
|
+
flowApiName: params.flowApiName,
|
|
207
|
+
apexClassName: params.apexClassName,
|
|
208
|
+
inputs: params.inputs ?? [],
|
|
209
|
+
outputs: params.outputs ?? [],
|
|
210
|
+
}, API_VERSION);
|
|
211
|
+
const deployId = await deployZip(auth, base64Zip);
|
|
212
|
+
const result = await pollDeployStatus(auth, deployId, 120_000);
|
|
213
|
+
return resultContent(result);
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
return resultContent({ success: false, message: err instanceof Error ? err.message : String(err) });
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// ─── DELETE ──────────────────────────────────────────────────────────────
|
|
221
|
+
server.registerTool("sf_delete_agent", {
|
|
222
|
+
title: "Delete Agentforce Agent",
|
|
223
|
+
description: `Cleanly deletes an Agentforce Agent (Bot) and optionally its related GenAiPlugin topics and GenAiFunction actions.
|
|
224
|
+
|
|
225
|
+
Deletion order (to avoid dependency errors):
|
|
226
|
+
1. Deactivates the agent
|
|
227
|
+
2. Reads the Bot → finds GenAiPlannerBundle name
|
|
228
|
+
3. Reads the GenAiPlannerBundle → finds GenAiPlugin topic names (if deleteTopics: true)
|
|
229
|
+
4. Reads each GenAiPlugin → finds GenAiFunction action names (if deleteActions: true)
|
|
230
|
+
5. Deletes GenAiFunctions → GenAiPlugins → GenAiPlannerBundle → Bot
|
|
231
|
+
|
|
232
|
+
Set deleteTopics: true to also delete the agent's topics.
|
|
233
|
+
Set deleteActions: true to also delete the actions (requires deleteTopics: true since action names are discovered via topics).
|
|
234
|
+
|
|
235
|
+
WARNING: Deleting actions/topics affects all agents that reference them.`,
|
|
236
|
+
inputSchema: DeleteAgentSchema,
|
|
237
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
238
|
+
}, async (params) => {
|
|
239
|
+
const auth = await getAuth();
|
|
240
|
+
const result = await deleteAgent(auth, params);
|
|
241
|
+
return resultContent(result);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// ─── TEST & DEBUG ─────────────────────────────────────────────────────────
|
|
245
|
+
server.registerTool("sf_test_agent", {
|
|
246
|
+
title: "Send Test Message to Agentforce Agent",
|
|
247
|
+
description: `Sends a test message to an Agentforce Agent via the Einstein Agent API and returns the agent's response. Useful for end-to-end testing without opening the Salesforce UI.
|
|
248
|
+
|
|
249
|
+
Flow:
|
|
250
|
+
1. Bootstraps an Agentforce session (POST /agentforce/bootstrap/nameduser)
|
|
251
|
+
2. Creates a session (POST /einstein/ai-agent/v1/agents/{id}/sessions)
|
|
252
|
+
3. Sends the message and returns the agent's reply text
|
|
253
|
+
|
|
254
|
+
The agent must be Active. Set orgUrl if your My Domain URL differs from SF_INSTANCE_URL.
|
|
255
|
+
|
|
256
|
+
Returns: response text, sessionId, and success status.`,
|
|
257
|
+
inputSchema: TestAgentSchema,
|
|
258
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
259
|
+
}, async (params) => {
|
|
260
|
+
const auth = await getAuth();
|
|
261
|
+
const result = await testAgent(auth, params);
|
|
262
|
+
return resultContent(result);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
server.registerTool("sf_get_agent_logs", {
|
|
266
|
+
title: "Get Agentforce Debug Logs",
|
|
267
|
+
description: `Queries ConversationDefinitionEventLog for Agentforce agent debug information. Shows topic routing decisions, action executions, and errors.
|
|
268
|
+
|
|
269
|
+
Key event types to look for:
|
|
270
|
+
- TopicClassificationSuccess — agent routed to a topic (routing is working)
|
|
271
|
+
- ActionExecuted — agent invoked an action (full pipeline working)
|
|
272
|
+
- TopicClassificationFail / Error — routing or execution failed
|
|
273
|
+
|
|
274
|
+
If you see TopicClassificationSuccess but NO ActionExecuted events, the agent is routing correctly but cannot invoke actions — check that GenAiFunction schema files exist (redeploy with sf_create_agent_action).
|
|
275
|
+
|
|
276
|
+
Requires ConversationDefinitionEventLog access (Event Monitoring or Agentforce debug enabled in the org).`,
|
|
277
|
+
inputSchema: GetAgentLogsSchema,
|
|
278
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
279
|
+
}, async (params) => {
|
|
280
|
+
const auth = await getAuth();
|
|
281
|
+
const result = await getAgentLogs(auth, params);
|
|
282
|
+
return resultContent(result);
|
|
283
|
+
});
|
|
125
284
|
}
|
|
126
285
|
//# sourceMappingURL=agentforce.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "salesforce-metadata-mcp",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.4",
|
|
4
4
|
"description": "The most comprehensive Salesforce metadata and development MCP server: 60+ tools for custom objects, flows, Apex, LWC, Agentforce, deployments, and more",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|