salesforce-agent 0.2.0__tar.gz
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.
- salesforce_agent-0.2.0/LICENSE +21 -0
- salesforce_agent-0.2.0/MANIFEST.in +4 -0
- salesforce_agent-0.2.0/PKG-INFO +215 -0
- salesforce_agent-0.2.0/README.md +183 -0
- salesforce_agent-0.2.0/pyproject.toml +58 -0
- salesforce_agent-0.2.0/requirements.txt +2 -0
- salesforce_agent-0.2.0/salesforce_agent/__init__.py +69 -0
- salesforce_agent-0.2.0/salesforce_agent/__main__.py +4 -0
- salesforce_agent-0.2.0/salesforce_agent/agent_server.py +77 -0
- salesforce_agent-0.2.0/salesforce_agent/api/__init__.py +17 -0
- salesforce_agent-0.2.0/salesforce_agent/api/api_client_admin.py +58 -0
- salesforce_agent-0.2.0/salesforce_agent/api/api_client_base.py +119 -0
- salesforce_agent-0.2.0/salesforce_agent/api/api_client_bulk.py +145 -0
- salesforce_agent-0.2.0/salesforce_agent/api/api_client_describe.py +40 -0
- salesforce_agent-0.2.0/salesforce_agent/api/api_client_query.py +68 -0
- salesforce_agent-0.2.0/salesforce_agent/api/api_client_records.py +164 -0
- salesforce_agent-0.2.0/salesforce_agent/api_client.py +47 -0
- salesforce_agent-0.2.0/salesforce_agent/auth.py +321 -0
- salesforce_agent-0.2.0/salesforce_agent/main_agent.json +14 -0
- salesforce_agent-0.2.0/salesforce_agent/mcp/__init__.py +19 -0
- salesforce_agent-0.2.0/salesforce_agent/mcp/mcp_salesforce.py +259 -0
- salesforce_agent-0.2.0/salesforce_agent/mcp_config.json +3 -0
- salesforce_agent-0.2.0/salesforce_agent/mcp_server.py +75 -0
- salesforce_agent-0.2.0/salesforce_agent/salesforce_input_models.py +161 -0
- salesforce_agent-0.2.0/salesforce_agent/salesforce_response_models.py +150 -0
- salesforce_agent-0.2.0/salesforce_agent.egg-info/PKG-INFO +215 -0
- salesforce_agent-0.2.0/salesforce_agent.egg-info/SOURCES.txt +43 -0
- salesforce_agent-0.2.0/salesforce_agent.egg-info/dependency_links.txt +1 -0
- salesforce_agent-0.2.0/salesforce_agent.egg-info/entry_points.txt +3 -0
- salesforce_agent-0.2.0/salesforce_agent.egg-info/requires.txt +21 -0
- salesforce_agent-0.2.0/salesforce_agent.egg-info/top_level.txt +1 -0
- salesforce_agent-0.2.0/setup.cfg +4 -0
- salesforce_agent-0.2.0/tests/test_admin.py +28 -0
- salesforce_agent-0.2.0/tests/test_api_wrapper.py +36 -0
- salesforce_agent-0.2.0/tests/test_auth.py +338 -0
- salesforce_agent-0.2.0/tests/test_bulk.py +133 -0
- salesforce_agent-0.2.0/tests/test_concept_parity.py +37 -0
- salesforce_agent-0.2.0/tests/test_describe.py +35 -0
- salesforce_agent-0.2.0/tests/test_errors.py +134 -0
- salesforce_agent-0.2.0/tests/test_init_dynamics.py +18 -0
- salesforce_agent-0.2.0/tests/test_query.py +82 -0
- salesforce_agent-0.2.0/tests/test_records.py +155 -0
- salesforce_agent-0.2.0/tests/test_salesforce_mcp_validation.py +273 -0
- salesforce_agent-0.2.0/tests/test_salesforce_models.py +62 -0
- salesforce_agent-0.2.0/tests/test_startup.py +33 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Advanced Agentic Coding
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: salesforce-agent
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Salesforce REST/SOQL/Bulk API 2.0 connector + MCP Server + A2A Server for Agentic AI!
|
|
5
|
+
Author-email: Audel Rouhi <knucklessg1@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: <3.15,>=3.11
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: agent-utilities>=0.48.0
|
|
16
|
+
Requires-Dist: httpx>=0.27.0
|
|
17
|
+
Provides-Extra: mcp
|
|
18
|
+
Requires-Dist: agent-utilities[mcp]>=0.48.0; extra == "mcp"
|
|
19
|
+
Provides-Extra: agent
|
|
20
|
+
Requires-Dist: agent-utilities[agent,logfire]>=0.48.0; extra == "agent"
|
|
21
|
+
Provides-Extra: jwt
|
|
22
|
+
Requires-Dist: cryptography>=42.0.0; extra == "jwt"
|
|
23
|
+
Provides-Extra: all
|
|
24
|
+
Requires-Dist: salesforce-agent[agent,jwt,logfire,mcp]>=0.2.0; extra == "all"
|
|
25
|
+
Provides-Extra: test
|
|
26
|
+
Requires-Dist: pytest-xdist>=3.6.0; extra == "test"
|
|
27
|
+
Requires-Dist: pytest; extra == "test"
|
|
28
|
+
Requires-Dist: pytest-asyncio; extra == "test"
|
|
29
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
30
|
+
Requires-Dist: cryptography>=42.0.0; extra == "test"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# Salesforce Agent
|
|
34
|
+
## CLI or API | MCP | Agent
|
|
35
|
+
|
|
36
|
+

|
|
37
|
+

|
|
38
|
+

|
|
39
|
+

|
|
40
|
+

|
|
41
|
+

|
|
42
|
+

|
|
43
|
+
|
|
44
|
+
*Version: 0.2.0*
|
|
45
|
+
|
|
46
|
+
> **Documentation** — Installation, deployment, usage across the API, CLI, and MCP
|
|
47
|
+
> server live on the docs site:
|
|
48
|
+
> <https://knuckles-team.github.io/salesforce-agent/>
|
|
49
|
+
|
|
50
|
+
## Table of Contents
|
|
51
|
+
|
|
52
|
+
- [Overview](#overview)
|
|
53
|
+
- [Architecture](#architecture)
|
|
54
|
+
- [Installation](#installation)
|
|
55
|
+
- [MCP Tools](#mcp-tools)
|
|
56
|
+
- [Auth Flows](#auth-flows)
|
|
57
|
+
- [Environment Variables](#environment-variables)
|
|
58
|
+
- [Quick Start](#quick-start)
|
|
59
|
+
- [Deployment](#deployment)
|
|
60
|
+
- [Development](#development)
|
|
61
|
+
- [License](#license)
|
|
62
|
+
|
|
63
|
+
## Overview
|
|
64
|
+
|
|
65
|
+
**The Salesforce connector for the agent-utilities fleet** — an owned thin
|
|
66
|
+
httpx wrapper over the Salesforce REST API exposed as a FastMCP server and an
|
|
67
|
+
A2A agent. REST + SOQL/SOSL + Bulk API 2.0 + metadata describe, with safety
|
|
68
|
+
gates designed for autonomous agents.
|
|
69
|
+
|
|
70
|
+
No `simple-salesforce`: every endpoint is a documented thin call with its
|
|
71
|
+
Salesforce API doc URL cited in the docstring.
|
|
72
|
+
|
|
73
|
+
## Architecture
|
|
74
|
+
|
|
75
|
+
```mermaid
|
|
76
|
+
graph TD
|
|
77
|
+
User([User/A2A]) --> Server[A2A Server / salesforce-agent]
|
|
78
|
+
Server --> Agent[Pydantic AI Agent]
|
|
79
|
+
Agent --> MCP[MCP Server / salesforce-mcp]
|
|
80
|
+
MCP --> Client[Api facade / httpx]
|
|
81
|
+
Client --> ExternalAPI([Salesforce REST API])
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Installation
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pip install salesforce-agent # core client only
|
|
88
|
+
pip install salesforce-agent[mcp] # + FastMCP server
|
|
89
|
+
pip install salesforce-agent[agent] # + Pydantic AI A2A agent server
|
|
90
|
+
pip install salesforce-agent[jwt] # + cryptography for the JWT bearer flow
|
|
91
|
+
pip install salesforce-agent[all] # everything
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Prebuilt Docker image: `knucklessg1/salesforce-agent:latest`.
|
|
95
|
+
|
|
96
|
+
## MCP Tools
|
|
97
|
+
|
|
98
|
+
Five consolidated, action-routed tools. Each takes `action` and `params_json`.
|
|
99
|
+
|
|
100
|
+
| Tool | Actions | Toggle |
|
|
101
|
+
|------|---------|--------|
|
|
102
|
+
| `salesforce_soql` | `query` (auto-pagination via `nextRecordsUrl`, capped), `query_all` (deleted/archived), `explain`, `search` (SOSL) | `SOQLTOOL` |
|
|
103
|
+
| `salesforce_records` | `get` (field selection), `create`, `update`, `upsert` (external id), `delete`*, `composite` (≤25 subrequests), `collections_create`/`collections_update` (≤200 records), `collections_delete`* | `RECORDSTOOL` |
|
|
104
|
+
| `salesforce_describe` | `global`, `sobject` (fields/relationships/picklists), `record_counts`, `limits` (API usage) | `DESCRIBETOOL` |
|
|
105
|
+
| `salesforce_bulk` | `create_job` (insert/update/upsert/`delete`*/`hardDelete`*), `upload` (CSV), `close`, `abort`, `status`, `list_jobs`, `delete_job`, `results` (successful/failed/unprocessed, size-capped) | `BULKTOOL` |
|
|
106
|
+
| `salesforce_admin` | `user_info`, `org_info`, `list_reports`, `run_report` (sync, capped). Listing/running Flows is **out of scope for v1**. | `ADMINTOOL` |
|
|
107
|
+
|
|
108
|
+
`*` Destructive — blocked unless `SALESFORCE_ALLOW_DESTRUCTIVE=true`.
|
|
109
|
+
|
|
110
|
+
## Auth Flows
|
|
111
|
+
|
|
112
|
+
| Flow | Credentials | Notes |
|
|
113
|
+
|------|-------------|-------|
|
|
114
|
+
| OAuth2 client-credentials | consumer key + secret + My Domain URL | default server-to-server flow |
|
|
115
|
+
| OAuth2 refresh-token | refresh token + consumer key | instance URL from token response |
|
|
116
|
+
| OAuth2 JWT bearer | consumer key + username + RSA key | `pip install salesforce-agent[jwt]` |
|
|
117
|
+
| Static access token | token + instance URL | testing / externally managed sessions |
|
|
118
|
+
|
|
119
|
+
Sandbox orgs: `SALESFORCE_SANDBOX=true` (`test.salesforce.com`). Tokens are
|
|
120
|
+
cached with expiry tracking and refreshed transparently (plus one retry on
|
|
121
|
+
401); secrets are redacted from all errors and logs.
|
|
122
|
+
|
|
123
|
+
## Environment Variables
|
|
124
|
+
|
|
125
|
+
| Variable | Default | Purpose |
|
|
126
|
+
|----------|---------|---------|
|
|
127
|
+
| `SALESFORCE_INSTANCE_URL` | — | My Domain instance URL (required for client-credentials and static tokens) |
|
|
128
|
+
| `SALESFORCE_LOGIN_URL` | derived | Override the OAuth login host |
|
|
129
|
+
| `SALESFORCE_SANDBOX` | `False` | Sandbox org (`test.salesforce.com`) |
|
|
130
|
+
| `SALESFORCE_API_VERSION` | `v62.0` | REST API version |
|
|
131
|
+
| `SALESFORCE_AUTH_FLOW` | auto | `client_credentials` / `refresh_token` / `jwt_bearer` / `access_token` |
|
|
132
|
+
| `SALESFORCE_CLIENT_ID` / `SALESFORCE_CLIENT_SECRET` | — | Connected App consumer key/secret |
|
|
133
|
+
| `SALESFORCE_REFRESH_TOKEN` | — | Refresh-token flow credential |
|
|
134
|
+
| `SALESFORCE_JWT_SUBJECT` / `SALESFORCE_JWT_PRIVATE_KEY[_PATH]` / `SALESFORCE_JWT_AUDIENCE` | — | JWT bearer flow |
|
|
135
|
+
| `SALESFORCE_ACCESS_TOKEN` | — | Static access token (testing) |
|
|
136
|
+
| `SALESFORCE_TOKEN_TTL_SECONDS` | `1800` | Cached-token TTL fallback |
|
|
137
|
+
| `SALESFORCE_SSL_VERIFY` | `True` | TLS verification |
|
|
138
|
+
| `SALESFORCE_TIMEOUT` | `30` | HTTP timeout (seconds) |
|
|
139
|
+
| `SALESFORCE_ALLOW_DESTRUCTIVE` | `False` | Gate for all delete paths |
|
|
140
|
+
| `SALESFORCE_MAX_QUERY_RECORDS` | `2000` | Per-call SOQL pagination cap |
|
|
141
|
+
| `SALESFORCE_BULK_RESULTS_MAX_BYTES` | `5000000` | Bulk result download cap |
|
|
142
|
+
| `SALESFORCE_REPORT_MAX_ROWS` | `2000` | Sync report detail-row note (platform cap) |
|
|
143
|
+
| `HOST` / `PORT` / `TRANSPORT` | `0.0.0.0` / `8000` / `stdio` | MCP server bind + transport |
|
|
144
|
+
| `SOQLTOOL` / `RECORDSTOOL` / `DESCRIBETOOL` / `BULKTOOL` / `ADMINTOOL` | `True` | Per-domain tool toggles |
|
|
145
|
+
| `ENABLE_OTEL` / `OTEL_EXPORTER_OTLP_*` | — | Telemetry (OTEL / Langfuse) |
|
|
146
|
+
| `EUNOMIA_TYPE` / `EUNOMIA_POLICY_FILE` / `EUNOMIA_REMOTE_URL` | `none` | MCP authorization middleware |
|
|
147
|
+
| `AUTH_TYPE` | `none` | MCP server auth mode (Docker) |
|
|
148
|
+
|
|
149
|
+
See `.env.example` for the full annotated list.
|
|
150
|
+
|
|
151
|
+
## Quick Start
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
pip install salesforce-agent[all]
|
|
155
|
+
cp .env.example .env # fill in one auth flow
|
|
156
|
+
salesforce-mcp # stdio MCP server
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from salesforce_agent import Api
|
|
161
|
+
|
|
162
|
+
api = Api() # configured from SALESFORCE_* env vars
|
|
163
|
+
rows = api.soql.query("SELECT Id, Name FROM Account", max_records=200)
|
|
164
|
+
api.records.upsert("Account", "External_Id__c", "X-1", {"Name": "Acme"})
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Typed tool-input contracts live in
|
|
168
|
+
`salesforce_agent.salesforce_input_models`; typed error envelopes in
|
|
169
|
+
`salesforce_agent.salesforce_response_models`.
|
|
170
|
+
|
|
171
|
+
## Deployment
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# MCP server only (port 8000, streamable-http, /health)
|
|
175
|
+
docker compose -f docker/mcp.compose.yml up -d
|
|
176
|
+
|
|
177
|
+
# MCP server + A2A agent server (agent on port 9020, AG-UI web interface)
|
|
178
|
+
docker compose -f docker/agent.compose.yml up -d
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The A2A agent server (`salesforce-agent` console script, `agent_server.py`)
|
|
182
|
+
reads `MCP_URL`, `PROVIDER`, and `MODEL_ID` from the environment. See
|
|
183
|
+
[docs/deployment.md](docs/deployment.md) for transports, reverse proxy, and
|
|
184
|
+
DNS guidance.
|
|
185
|
+
|
|
186
|
+
See [docs/](docs/index.md) for the full overview, installation, usage, and
|
|
187
|
+
deployment guides; concept registry in [docs/concepts.md](docs/concepts.md)
|
|
188
|
+
(`CONCEPT:SFDC-1.x`).
|
|
189
|
+
|
|
190
|
+
<!-- BEGIN GENERATED: additional-deployment-options -->
|
|
191
|
+
### Additional Deployment Options
|
|
192
|
+
|
|
193
|
+
`salesforce-agent` can also run as a **local container** (Docker / Podman / `uv`) or be
|
|
194
|
+
consumed from a **remote deployment**. The
|
|
195
|
+
[Deployment guide](https://knuckles-team.github.io/salesforce-agent/deployment/) has full, copy-paste
|
|
196
|
+
`mcp_config.json` for all four transports — **stdio**, **streamable-http**,
|
|
197
|
+
**local container / uv**, and **remote URL**:
|
|
198
|
+
|
|
199
|
+
- **Local container / uv** — launch the server from `mcp_config.json` via `uvx`,
|
|
200
|
+
`docker run`, or `podman run`, or point at a local streamable-http container by `url`.
|
|
201
|
+
- **Remote URL** — connect to a server deployed behind Caddy at
|
|
202
|
+
`http://salesforce-mcp.arpa/mcp` using the `"url"` key.
|
|
203
|
+
<!-- END GENERATED: additional-deployment-options -->
|
|
204
|
+
|
|
205
|
+
## Development
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
pip install -e .[all,test]
|
|
209
|
+
pytest # mocked httpx suite — no live org required
|
|
210
|
+
pre-commit run --all-files # must be fully green before committing
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Salesforce Agent
|
|
2
|
+
## CLI or API | MCP | Agent
|
|
3
|
+
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
*Version: 0.2.0*
|
|
13
|
+
|
|
14
|
+
> **Documentation** — Installation, deployment, usage across the API, CLI, and MCP
|
|
15
|
+
> server live on the docs site:
|
|
16
|
+
> <https://knuckles-team.github.io/salesforce-agent/>
|
|
17
|
+
|
|
18
|
+
## Table of Contents
|
|
19
|
+
|
|
20
|
+
- [Overview](#overview)
|
|
21
|
+
- [Architecture](#architecture)
|
|
22
|
+
- [Installation](#installation)
|
|
23
|
+
- [MCP Tools](#mcp-tools)
|
|
24
|
+
- [Auth Flows](#auth-flows)
|
|
25
|
+
- [Environment Variables](#environment-variables)
|
|
26
|
+
- [Quick Start](#quick-start)
|
|
27
|
+
- [Deployment](#deployment)
|
|
28
|
+
- [Development](#development)
|
|
29
|
+
- [License](#license)
|
|
30
|
+
|
|
31
|
+
## Overview
|
|
32
|
+
|
|
33
|
+
**The Salesforce connector for the agent-utilities fleet** — an owned thin
|
|
34
|
+
httpx wrapper over the Salesforce REST API exposed as a FastMCP server and an
|
|
35
|
+
A2A agent. REST + SOQL/SOSL + Bulk API 2.0 + metadata describe, with safety
|
|
36
|
+
gates designed for autonomous agents.
|
|
37
|
+
|
|
38
|
+
No `simple-salesforce`: every endpoint is a documented thin call with its
|
|
39
|
+
Salesforce API doc URL cited in the docstring.
|
|
40
|
+
|
|
41
|
+
## Architecture
|
|
42
|
+
|
|
43
|
+
```mermaid
|
|
44
|
+
graph TD
|
|
45
|
+
User([User/A2A]) --> Server[A2A Server / salesforce-agent]
|
|
46
|
+
Server --> Agent[Pydantic AI Agent]
|
|
47
|
+
Agent --> MCP[MCP Server / salesforce-mcp]
|
|
48
|
+
MCP --> Client[Api facade / httpx]
|
|
49
|
+
Client --> ExternalAPI([Salesforce REST API])
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install salesforce-agent # core client only
|
|
56
|
+
pip install salesforce-agent[mcp] # + FastMCP server
|
|
57
|
+
pip install salesforce-agent[agent] # + Pydantic AI A2A agent server
|
|
58
|
+
pip install salesforce-agent[jwt] # + cryptography for the JWT bearer flow
|
|
59
|
+
pip install salesforce-agent[all] # everything
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Prebuilt Docker image: `knucklessg1/salesforce-agent:latest`.
|
|
63
|
+
|
|
64
|
+
## MCP Tools
|
|
65
|
+
|
|
66
|
+
Five consolidated, action-routed tools. Each takes `action` and `params_json`.
|
|
67
|
+
|
|
68
|
+
| Tool | Actions | Toggle |
|
|
69
|
+
|------|---------|--------|
|
|
70
|
+
| `salesforce_soql` | `query` (auto-pagination via `nextRecordsUrl`, capped), `query_all` (deleted/archived), `explain`, `search` (SOSL) | `SOQLTOOL` |
|
|
71
|
+
| `salesforce_records` | `get` (field selection), `create`, `update`, `upsert` (external id), `delete`*, `composite` (≤25 subrequests), `collections_create`/`collections_update` (≤200 records), `collections_delete`* | `RECORDSTOOL` |
|
|
72
|
+
| `salesforce_describe` | `global`, `sobject` (fields/relationships/picklists), `record_counts`, `limits` (API usage) | `DESCRIBETOOL` |
|
|
73
|
+
| `salesforce_bulk` | `create_job` (insert/update/upsert/`delete`*/`hardDelete`*), `upload` (CSV), `close`, `abort`, `status`, `list_jobs`, `delete_job`, `results` (successful/failed/unprocessed, size-capped) | `BULKTOOL` |
|
|
74
|
+
| `salesforce_admin` | `user_info`, `org_info`, `list_reports`, `run_report` (sync, capped). Listing/running Flows is **out of scope for v1**. | `ADMINTOOL` |
|
|
75
|
+
|
|
76
|
+
`*` Destructive — blocked unless `SALESFORCE_ALLOW_DESTRUCTIVE=true`.
|
|
77
|
+
|
|
78
|
+
## Auth Flows
|
|
79
|
+
|
|
80
|
+
| Flow | Credentials | Notes |
|
|
81
|
+
|------|-------------|-------|
|
|
82
|
+
| OAuth2 client-credentials | consumer key + secret + My Domain URL | default server-to-server flow |
|
|
83
|
+
| OAuth2 refresh-token | refresh token + consumer key | instance URL from token response |
|
|
84
|
+
| OAuth2 JWT bearer | consumer key + username + RSA key | `pip install salesforce-agent[jwt]` |
|
|
85
|
+
| Static access token | token + instance URL | testing / externally managed sessions |
|
|
86
|
+
|
|
87
|
+
Sandbox orgs: `SALESFORCE_SANDBOX=true` (`test.salesforce.com`). Tokens are
|
|
88
|
+
cached with expiry tracking and refreshed transparently (plus one retry on
|
|
89
|
+
401); secrets are redacted from all errors and logs.
|
|
90
|
+
|
|
91
|
+
## Environment Variables
|
|
92
|
+
|
|
93
|
+
| Variable | Default | Purpose |
|
|
94
|
+
|----------|---------|---------|
|
|
95
|
+
| `SALESFORCE_INSTANCE_URL` | — | My Domain instance URL (required for client-credentials and static tokens) |
|
|
96
|
+
| `SALESFORCE_LOGIN_URL` | derived | Override the OAuth login host |
|
|
97
|
+
| `SALESFORCE_SANDBOX` | `False` | Sandbox org (`test.salesforce.com`) |
|
|
98
|
+
| `SALESFORCE_API_VERSION` | `v62.0` | REST API version |
|
|
99
|
+
| `SALESFORCE_AUTH_FLOW` | auto | `client_credentials` / `refresh_token` / `jwt_bearer` / `access_token` |
|
|
100
|
+
| `SALESFORCE_CLIENT_ID` / `SALESFORCE_CLIENT_SECRET` | — | Connected App consumer key/secret |
|
|
101
|
+
| `SALESFORCE_REFRESH_TOKEN` | — | Refresh-token flow credential |
|
|
102
|
+
| `SALESFORCE_JWT_SUBJECT` / `SALESFORCE_JWT_PRIVATE_KEY[_PATH]` / `SALESFORCE_JWT_AUDIENCE` | — | JWT bearer flow |
|
|
103
|
+
| `SALESFORCE_ACCESS_TOKEN` | — | Static access token (testing) |
|
|
104
|
+
| `SALESFORCE_TOKEN_TTL_SECONDS` | `1800` | Cached-token TTL fallback |
|
|
105
|
+
| `SALESFORCE_SSL_VERIFY` | `True` | TLS verification |
|
|
106
|
+
| `SALESFORCE_TIMEOUT` | `30` | HTTP timeout (seconds) |
|
|
107
|
+
| `SALESFORCE_ALLOW_DESTRUCTIVE` | `False` | Gate for all delete paths |
|
|
108
|
+
| `SALESFORCE_MAX_QUERY_RECORDS` | `2000` | Per-call SOQL pagination cap |
|
|
109
|
+
| `SALESFORCE_BULK_RESULTS_MAX_BYTES` | `5000000` | Bulk result download cap |
|
|
110
|
+
| `SALESFORCE_REPORT_MAX_ROWS` | `2000` | Sync report detail-row note (platform cap) |
|
|
111
|
+
| `HOST` / `PORT` / `TRANSPORT` | `0.0.0.0` / `8000` / `stdio` | MCP server bind + transport |
|
|
112
|
+
| `SOQLTOOL` / `RECORDSTOOL` / `DESCRIBETOOL` / `BULKTOOL` / `ADMINTOOL` | `True` | Per-domain tool toggles |
|
|
113
|
+
| `ENABLE_OTEL` / `OTEL_EXPORTER_OTLP_*` | — | Telemetry (OTEL / Langfuse) |
|
|
114
|
+
| `EUNOMIA_TYPE` / `EUNOMIA_POLICY_FILE` / `EUNOMIA_REMOTE_URL` | `none` | MCP authorization middleware |
|
|
115
|
+
| `AUTH_TYPE` | `none` | MCP server auth mode (Docker) |
|
|
116
|
+
|
|
117
|
+
See `.env.example` for the full annotated list.
|
|
118
|
+
|
|
119
|
+
## Quick Start
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
pip install salesforce-agent[all]
|
|
123
|
+
cp .env.example .env # fill in one auth flow
|
|
124
|
+
salesforce-mcp # stdio MCP server
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from salesforce_agent import Api
|
|
129
|
+
|
|
130
|
+
api = Api() # configured from SALESFORCE_* env vars
|
|
131
|
+
rows = api.soql.query("SELECT Id, Name FROM Account", max_records=200)
|
|
132
|
+
api.records.upsert("Account", "External_Id__c", "X-1", {"Name": "Acme"})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Typed tool-input contracts live in
|
|
136
|
+
`salesforce_agent.salesforce_input_models`; typed error envelopes in
|
|
137
|
+
`salesforce_agent.salesforce_response_models`.
|
|
138
|
+
|
|
139
|
+
## Deployment
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# MCP server only (port 8000, streamable-http, /health)
|
|
143
|
+
docker compose -f docker/mcp.compose.yml up -d
|
|
144
|
+
|
|
145
|
+
# MCP server + A2A agent server (agent on port 9020, AG-UI web interface)
|
|
146
|
+
docker compose -f docker/agent.compose.yml up -d
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The A2A agent server (`salesforce-agent` console script, `agent_server.py`)
|
|
150
|
+
reads `MCP_URL`, `PROVIDER`, and `MODEL_ID` from the environment. See
|
|
151
|
+
[docs/deployment.md](docs/deployment.md) for transports, reverse proxy, and
|
|
152
|
+
DNS guidance.
|
|
153
|
+
|
|
154
|
+
See [docs/](docs/index.md) for the full overview, installation, usage, and
|
|
155
|
+
deployment guides; concept registry in [docs/concepts.md](docs/concepts.md)
|
|
156
|
+
(`CONCEPT:SFDC-1.x`).
|
|
157
|
+
|
|
158
|
+
<!-- BEGIN GENERATED: additional-deployment-options -->
|
|
159
|
+
### Additional Deployment Options
|
|
160
|
+
|
|
161
|
+
`salesforce-agent` can also run as a **local container** (Docker / Podman / `uv`) or be
|
|
162
|
+
consumed from a **remote deployment**. The
|
|
163
|
+
[Deployment guide](https://knuckles-team.github.io/salesforce-agent/deployment/) has full, copy-paste
|
|
164
|
+
`mcp_config.json` for all four transports — **stdio**, **streamable-http**,
|
|
165
|
+
**local container / uv**, and **remote URL**:
|
|
166
|
+
|
|
167
|
+
- **Local container / uv** — launch the server from `mcp_config.json` via `uvx`,
|
|
168
|
+
`docker run`, or `podman run`, or point at a local streamable-http container by `url`.
|
|
169
|
+
- **Remote URL** — connect to a server deployed behind Caddy at
|
|
170
|
+
`http://salesforce-mcp.arpa/mcp` using the `"url"` key.
|
|
171
|
+
<!-- END GENERATED: additional-deployment-options -->
|
|
172
|
+
|
|
173
|
+
## Development
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
pip install -e .[all,test]
|
|
177
|
+
pytest # mocked httpx suite — no live org required
|
|
178
|
+
pre-commit run --all-files # must be fully green before committing
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [ "setuptools>=80.9.0", "wheel",]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "salesforce-agent"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Salesforce REST/SOQL/Bulk API 2.0 connector + MCP Server + A2A Server for Agentic AI!"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
classifiers = [ "Development Status :: 4 - Beta", "License :: OSI Approved :: MIT License", "Environment :: Console", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3",]
|
|
11
|
+
requires-python = ">=3.11, <3.15"
|
|
12
|
+
dependencies = [ "agent-utilities>=0.48.0", "httpx>=0.27.0",]
|
|
13
|
+
[[project.authors]]
|
|
14
|
+
name = "Audel Rouhi"
|
|
15
|
+
email = "knucklessg1@gmail.com"
|
|
16
|
+
|
|
17
|
+
[project.license]
|
|
18
|
+
text = "MIT"
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
mcp = [ "agent-utilities[mcp]>=0.48.0",]
|
|
22
|
+
agent = [ "agent-utilities[agent,logfire]>=0.48.0",]
|
|
23
|
+
jwt = [ "cryptography>=42.0.0",]
|
|
24
|
+
all = [ "salesforce-agent[mcp,agent,jwt,logfire]>=0.2.0",]
|
|
25
|
+
test = [ "pytest-xdist>=3.6.0", "pytest", "pytest-asyncio", "pytest-cov", "cryptography>=42.0.0",]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
salesforce-mcp = "salesforce_agent.mcp_server:mcp_server"
|
|
29
|
+
salesforce-agent = "salesforce_agent.agent_server:agent_server"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools]
|
|
32
|
+
include-package-data = true
|
|
33
|
+
|
|
34
|
+
[tool.ruff]
|
|
35
|
+
line-length = 88
|
|
36
|
+
target-version = "py310"
|
|
37
|
+
|
|
38
|
+
[tool.mypy]
|
|
39
|
+
python_version = "3.10"
|
|
40
|
+
ignore_missing_imports = true
|
|
41
|
+
check_untyped_defs = true
|
|
42
|
+
|
|
43
|
+
[dependency-groups]
|
|
44
|
+
dev = [ "pytest-timeout>=2.4.0",]
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.package-data]
|
|
47
|
+
salesforce_agent = [ "mcp_config.json", "agent_data/**",]
|
|
48
|
+
|
|
49
|
+
[tool.ruff.lint]
|
|
50
|
+
select = [ "E", "F", "I", "UP", "B",]
|
|
51
|
+
ignore = [ "E402", "E501", "B008",]
|
|
52
|
+
|
|
53
|
+
[tool.setuptools.packages.find]
|
|
54
|
+
where = [ ".",]
|
|
55
|
+
include = [ "salesforce_agent*",]
|
|
56
|
+
|
|
57
|
+
[tool.vulture]
|
|
58
|
+
ignore_names = ["request", "config", "exc_info"]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""CONCEPT:ECO-4.0 Unified ecosystem initialization dynamic check."""
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import inspect
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
__version__ = "0.2.0"
|
|
8
|
+
__all__: list[str] = []
|
|
9
|
+
|
|
10
|
+
CORE_MODULES = [
|
|
11
|
+
"salesforce_agent.salesforce_response_models",
|
|
12
|
+
"salesforce_agent.salesforce_input_models",
|
|
13
|
+
"salesforce_agent.auth",
|
|
14
|
+
"salesforce_agent.api_client",
|
|
15
|
+
]
|
|
16
|
+
OPTIONAL_MODULES = {
|
|
17
|
+
"salesforce_agent.agent_server": "agent",
|
|
18
|
+
"salesforce_agent.mcp_server": "mcp",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _expose_members(module):
|
|
23
|
+
for name, obj in inspect.getmembers(module):
|
|
24
|
+
if (inspect.isclass(obj) or inspect.isfunction(obj)) and not name.startswith(
|
|
25
|
+
"_"
|
|
26
|
+
):
|
|
27
|
+
globals()[name] = obj
|
|
28
|
+
if name not in __all__:
|
|
29
|
+
__all__.append(name)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
for module_name in CORE_MODULES:
|
|
33
|
+
module = importlib.import_module(module_name)
|
|
34
|
+
_expose_members(module)
|
|
35
|
+
|
|
36
|
+
_loaded_optional_modules: dict[str, Any] = {}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _import_module_safely(module_name: str):
|
|
40
|
+
try:
|
|
41
|
+
return importlib.import_module(module_name)
|
|
42
|
+
except ImportError:
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def __getattr__(name: str) -> Any:
|
|
47
|
+
if name == "_MCP_AVAILABLE":
|
|
48
|
+
mcp_key = next((k for k in OPTIONAL_MODULES if "mcp_server" in k), None)
|
|
49
|
+
return _import_module_safely(mcp_key) is not None if mcp_key else False
|
|
50
|
+
if name == "_AGENT_AVAILABLE":
|
|
51
|
+
agent_key = next((k for k in OPTIONAL_MODULES if "agent_server" in k), None)
|
|
52
|
+
return _import_module_safely(agent_key) is not None if agent_key else False
|
|
53
|
+
|
|
54
|
+
for module_name in OPTIONAL_MODULES:
|
|
55
|
+
if module_name not in _loaded_optional_modules:
|
|
56
|
+
module = _import_module_safely(module_name)
|
|
57
|
+
if module is not None:
|
|
58
|
+
_loaded_optional_modules[module_name] = module
|
|
59
|
+
_expose_members(module)
|
|
60
|
+
|
|
61
|
+
module = _loaded_optional_modules.get(module_name)
|
|
62
|
+
if module is not None and hasattr(module, name):
|
|
63
|
+
return getattr(module, name)
|
|
64
|
+
|
|
65
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def __dir__() -> list[str]:
|
|
69
|
+
return sorted(list(globals().keys()) + __all__)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Pydantic AI A2A agent server for the Salesforce connector."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import warnings
|
|
7
|
+
|
|
8
|
+
__version__ = "0.2.0"
|
|
9
|
+
|
|
10
|
+
logging.basicConfig(
|
|
11
|
+
level=logging.INFO,
|
|
12
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
13
|
+
)
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
DEFAULT_AGENT_NAME = None
|
|
17
|
+
DEFAULT_AGENT_DESCRIPTION = None
|
|
18
|
+
DEFAULT_AGENT_SYSTEM_PROMPT = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def agent_server():
|
|
22
|
+
"""Start graph-based Pydantic AI agent server."""
|
|
23
|
+
from agent_utilities import (
|
|
24
|
+
build_system_prompt_from_workspace,
|
|
25
|
+
create_agent_parser,
|
|
26
|
+
create_agent_server,
|
|
27
|
+
initialize_workspace,
|
|
28
|
+
load_identity,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
global DEFAULT_AGENT_NAME, DEFAULT_AGENT_DESCRIPTION, DEFAULT_AGENT_SYSTEM_PROMPT
|
|
32
|
+
initialize_workspace()
|
|
33
|
+
meta = load_identity()
|
|
34
|
+
DEFAULT_AGENT_NAME = os.getenv(
|
|
35
|
+
"DEFAULT_AGENT_NAME", meta.get("name", "Salesforce Agent")
|
|
36
|
+
)
|
|
37
|
+
DEFAULT_AGENT_DESCRIPTION = os.getenv(
|
|
38
|
+
"AGENT_DESCRIPTION",
|
|
39
|
+
meta.get("description", "AI agent for Salesforce CRM operations."),
|
|
40
|
+
)
|
|
41
|
+
DEFAULT_AGENT_SYSTEM_PROMPT = os.getenv(
|
|
42
|
+
"AGENT_SYSTEM_PROMPT",
|
|
43
|
+
meta.get("content") or build_system_prompt_from_workspace(),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
warnings.filterwarnings("ignore", message=".*urllib3.*")
|
|
47
|
+
warnings.filterwarnings("ignore", category=DeprecationWarning, module="fastmcp")
|
|
48
|
+
|
|
49
|
+
print(f"{DEFAULT_AGENT_NAME} v{__version__}", file=sys.stderr)
|
|
50
|
+
parser = create_agent_parser()
|
|
51
|
+
args = parser.parse_args()
|
|
52
|
+
|
|
53
|
+
create_agent_server(
|
|
54
|
+
mcp_url=args.mcp_url,
|
|
55
|
+
mcp_config=args.mcp_config or "mcp_config.json",
|
|
56
|
+
host=args.host,
|
|
57
|
+
port=args.port,
|
|
58
|
+
provider=args.provider,
|
|
59
|
+
model_id=args.model_id,
|
|
60
|
+
router_model=args.model_id,
|
|
61
|
+
agent_model=args.model_id,
|
|
62
|
+
base_url=args.base_url,
|
|
63
|
+
api_key=args.api_key,
|
|
64
|
+
custom_skills_directory=args.custom_skills_directory,
|
|
65
|
+
enable_web_ui=args.web,
|
|
66
|
+
enable_otel=args.otel,
|
|
67
|
+
otel_endpoint=args.otel_endpoint,
|
|
68
|
+
otel_headers=args.otel_headers,
|
|
69
|
+
otel_public_key=args.otel_public_key,
|
|
70
|
+
otel_secret_key=args.otel_secret_key,
|
|
71
|
+
otel_protocol=args.otel_protocol,
|
|
72
|
+
debug=args.debug,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
agent_server()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Salesforce REST API resource clients (owned thin httpx wrappers)."""
|
|
2
|
+
|
|
3
|
+
from salesforce_agent.api.api_client_admin import AdminClient
|
|
4
|
+
from salesforce_agent.api.api_client_base import ApiClientBase
|
|
5
|
+
from salesforce_agent.api.api_client_bulk import BulkClient
|
|
6
|
+
from salesforce_agent.api.api_client_describe import DescribeClient
|
|
7
|
+
from salesforce_agent.api.api_client_query import QueryClient
|
|
8
|
+
from salesforce_agent.api.api_client_records import RecordsClient
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"AdminClient",
|
|
12
|
+
"ApiClientBase",
|
|
13
|
+
"BulkClient",
|
|
14
|
+
"DescribeClient",
|
|
15
|
+
"QueryClient",
|
|
16
|
+
"RecordsClient",
|
|
17
|
+
]
|