# Directory Structure
```
├── .env
├── .env.example
├── .gitignore
├── agentforce_client.py
├── agentforce_mcp_server.py
├── github_push.sh
├── README.md
├── requirements.txt
├── session_manager.py
├── setup.sh
└── test_agentforce.py
```
# Files
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
```
1 | SALESFORCE_ORG_ID="PLACEHOLDER_FOR_ORG_ID"
2 | SALESFORCE_AGENT_ID="PLACEHOLDER_FOR_AGENT_ID"
3 | SALESFORCE_CLIENT_ID="PLACEHOLDER_FOR_CLIENT_ID"
4 | SALESFORCE_CLIENT_SECRET="PLACEHOLDER_FOR_CLIENT_SECRET"
5 | SALESFORCE_SERVER_URL="PLACEHOLDER_FOR_SERVER_URL"
6 |
```
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
```
1 | # Salesforce Agentforce API configuration
2 | SALESFORCE_ORG_ID="your_org_id_here"
3 | SALESFORCE_AGENT_ID="your_agent_id_here"
4 | SALESFORCE_CLIENT_ID="your_client_id_here"
5 | SALESFORCE_CLIENT_SECRET="your_client_secret_here"
6 | SALESFORCE_SERVER_URL="your_server_url_here"
7 |
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Environment variables
2 | .env
3 |
4 | # Virtual environment
5 | venv/
6 |
7 | # Python cache files
8 | __pycache__/
9 | *.py[cod]
10 | *$py.class
11 | *.so
12 | .Python
13 | .pytest_cache/
14 |
15 | # Distribution / packaging
16 | dist/
17 | build/
18 | *.egg-info/
19 |
20 | # Logs
21 | *.log
22 |
23 | # OS generated files
24 | .DS_Store
25 | .DS_Store?
26 | ._*
27 | .Spotlight-V100
28 | .Trashes
29 | ehthumbs.db
30 | Thumbs.db
31 |
32 | # IDE specific files
33 | .idea/
34 | .vscode/
35 | *.swp
36 | *.swo
37 |
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Agentforce MCP Server
2 |
3 | This MCP server provides tools to interact with the Salesforce Agentforce API. It allows authentication, session creation, and message exchange with Agentforce agents.
4 |
5 | ## Getting Started After Cloning
6 |
7 | If you've just cloned this repository, you can use the interactive setup script to quickly configure and run the server:
8 |
9 | ```bash
10 | chmod +x setup.sh
11 | ./setup.sh
12 | ```
13 |
14 | The setup script will:
15 | 1. Check your Python version
16 | 2. Install required dependencies
17 | 3. Guide you through entering your Salesforce credentials
18 | 4. Test your connection to Salesforce
19 | 5. Offer to start the server
20 | 6. Provide instructions for configuring Claude Desktop
21 |
22 | Alternatively, you can follow these manual steps:
23 |
24 | 1. **Install dependencies**:
25 | ```bash
26 | pip install -r requirements.txt
27 | ```
28 |
29 | 2. **Set up your environment variables**:
30 | ```bash
31 | cp .env.example .env
32 | ```
33 |
34 | 3. **Collect your Salesforce credentials**:
35 | - **SALESFORCE_ORG_ID**: Your 18-character Salesforce Org ID
36 | - **SALESFORCE_AGENT_ID**: The 18-character Agent ID from your Agentforce agent
37 | - **SALESFORCE_CLIENT_ID**: The Consumer Key from your Connected App
38 | - **SALESFORCE_CLIENT_SECRET**: The Consumer Secret from your Connected App
39 | - **SALESFORCE_SERVER_URL**: Your Salesforce My Domain URL without https:// prefix
40 |
41 | 4. **Edit your .env file** with the collected credentials:
42 | ```
43 | SALESFORCE_ORG_ID="00D5f000000J2PKEA0"
44 | SALESFORCE_AGENT_ID="0XxHn000000x9F1KAI"
45 | SALESFORCE_CLIENT_ID="3MVG9OGq41FnYVsFgnaG0AzJDWnoy37Bb18e0R.GgDJu2qB9sqppVl7ehWmJhGvPSLrrA0cBNhDJdsbZXnv52"
46 | SALESFORCE_CLIENT_SECRET="210117AC36E9E4C8AFCA02FF062B8A677BACBFFB71D2BB1162D60D316382FADE"
47 | SALESFORCE_SERVER_URL="example.my.salesforce.com"
48 | ```
49 | (Note: These are fictional example values. Replace with your actual credentials.)
50 |
51 | 5. **Make the server script executable**:
52 | ```bash
53 | chmod +x agentforce_mcp_server.py
54 | ```
55 |
56 | 6. **Run the server**:
57 | ```bash
58 | python agentforce_mcp_server.py
59 | ```
60 |
61 | For detailed instructions on finding your Salesforce credentials, see the [Setting Up Salesforce](#setting-up-salesforce) section below.
62 |
63 | ## Setup
64 |
65 | 1. Ensure you have Python 3.10 or higher installed.
66 |
67 | 2. Install the required dependencies:
68 | ```bash
69 | pip install -r requirements.txt
70 | ```
71 |
72 | 3. Make the server script executable:
73 | ```bash
74 | chmod +x agentforce_mcp_server.py
75 | ```
76 |
77 | ## Configuration
78 |
79 | The server uses environment variables for configuration. These are loaded from the `.env` file.
80 |
81 | 1. Copy the example environment file to create your own:
82 | ```bash
83 | cp .env.example .env
84 | ```
85 |
86 | 2. Edit the `.env` file and fill in your values:
87 | ```
88 | SALESFORCE_ORG_ID="your_org_id_here"
89 | SALESFORCE_AGENT_ID="your_agent_id_here" # The 18-character Agent ID you found in Salesforce
90 | SALESFORCE_CLIENT_ID="your_client_id_here" # The Consumer Key from your Connected App
91 | SALESFORCE_CLIENT_SECRET="your_client_secret_here" # The Consumer Secret from your Connected App
92 | SALESFORCE_SERVER_URL="your_server_url_here" # Your My Domain URL (e.g., example.my.salesforce.com)
93 | ```
94 |
95 | ## Salesforce Configuration
96 |
97 | To use the Agentforce API, you need to:
98 |
99 | 1. Create a Connected App in your Salesforce org
100 | 2. Find your Agentforce Agent ID
101 | 3. Note your Salesforce My Domain URL
102 |
103 | For detailed instructions on these steps, see the [Setting Up Salesforce](#setting-up-salesforce) section below.
104 |
105 | ## Running the Server
106 |
107 | Run the server using:
108 |
109 | ```bash
110 | python agentforce_mcp_server.py
111 | ```
112 |
113 | ## Available Tools
114 |
115 | The MCP server exposes the following tools:
116 |
117 | ### 1. `authenticate`
118 |
119 | Authenticates with the Agentforce API using a client email.
120 |
121 | Parameters:
122 | - `client_email`: Email of the client for authentication
123 |
124 | ### 2. `create_agent_session`
125 |
126 | Creates a session with the configured Agentforce agent.
127 |
128 | Parameters:
129 | - `client_email`: Email of the authenticated client
130 |
131 | ### 3. `send_message_to_agent`
132 |
133 | Sends a message to the Agentforce agent and returns the response.
134 |
135 | Parameters:
136 | - `client_email`: Email of the authenticated client
137 | - `message`: Message to send to the agent
138 |
139 | ### 4. `get_session_status`
140 |
141 | Gets the status of the current session, including authentication status, session ID, and sequence ID.
142 |
143 | Parameters:
144 | - `client_email`: Email of the authenticated client
145 |
146 | ### 5. `complete_agentforce_conversation`
147 |
148 | Convenience method that handles the complete flow - authentication, session creation, and message sending.
149 |
150 | Parameters:
151 | - `client_email`: Email of the client for authentication
152 | - `user_query`: Message to send to the agent
153 |
154 | ## Using with Claude for Desktop
155 |
156 | To use this server with Claude for Desktop, update your `claude_desktop_config.json` file:
157 |
158 | ```json
159 | {
160 | "mcpServers": {
161 | "agentforce": {
162 | "command": "python",
163 | "args": [
164 | "/path/to/your/agentforce_mcp_server.py"
165 | ]
166 | }
167 | }
168 | }
169 | ```
170 |
171 | Replace the path with the absolute path to the server script on your machine.
172 |
173 | ### Path Locations by Platform
174 |
175 | #### macOS
176 | - Configuration file: `~/Library/Application Support/Claude/claude_desktop_config.json`
177 | - Example path: `/Users/yourusername/Projects/agentforce-mcp-server/agentforce_mcp_server.py`
178 |
179 | #### Windows
180 | - Configuration file: `%APPDATA%\Claude\claude_desktop_config.json`
181 | - Example path: `C:\Users\yourusername\Projects\agentforce-mcp-server\agentforce_mcp_server.py`
182 |
183 | ## Setting Up Salesforce
184 |
185 | ### Creating a Connected App
186 |
187 | To use the Agentforce API, you need to create a Connected App in your Salesforce org:
188 |
189 | 1. Log in to your Salesforce org as an administrator
190 | 2. Go to **Setup**
191 | 3. In the Quick Find box, search for "App Manager" and click on it
192 | 4. Click the **New Connected App** button
193 | 5. Fill in the basic information:
194 | - **Connected App Name**: Agentforce MCP Integration (or any name you prefer)
195 | - **API Name**: Agentforce_MCP_Integration (this will be auto-filled)
196 | - **Contact Email**: Your email address
197 | 6. Check **Enable OAuth Settings**
198 | 7. Set the **Callback URL** to `https://localhost/oauth/callback` (this is not used but required)
199 | 8. Under **Selected OAuth Scopes**, add:
200 | - Manage user data via APIs (api)
201 | - Perform requests at any time (refresh_token, offline_access)
202 | 9. Click **Save**
203 | 10. After saving, you'll be redirected to the Connected App detail page
204 | 11. Note the **Consumer Key** (this is your client ID) and click **Click to reveal** next to **Consumer Secret** to get your client secret
205 |
206 | ### Finding Your Agent ID
207 |
208 | To find your Agentforce Agent ID:
209 |
210 | 1. Log in to your Salesforce org
211 | 2. Navigate to **Einstein Agent Builder**
212 | 3. Select the agent you want to use
213 | 4. Look at the URL in your browser - it will contain the Agent ID in the format: `https://your-salesforce-instance.lightning.force.com/lightning/r/Agent__c/0XxXXXXXXXXXXXXX/view`
214 | 5. The Agent ID is that 18-character ID (`0XxXXXXXXXXXXXXX`) in the URL
215 |
216 | ### Finding Your Salesforce My Domain URL
217 |
218 | To find your Salesforce My Domain URL:
219 |
220 | 1. Log in to your Salesforce org
221 | 2. Go to **Setup**
222 | 3. In the Quick Find box, search for "My Domain" and click on it
223 | 4. You'll see your domain in the format `DOMAIN-NAME.my.salesforce.com`
224 | 5. Use this URL without the "https://" prefix in your .env file
225 |
226 | ### Finding Your Org ID
227 |
228 | To find your Salesforce Org ID:
229 |
230 | 1. Log in to your Salesforce org
231 | 2. Go to **Setup**
232 | 3. In the Quick Find box, search for "Company Information" and click on it
233 | 4. Look for the "Organization ID" field - this is your Salesforce Org ID
234 | 5. It will be a 15 or 18-character alphanumeric string
235 |
236 | ## Notes
237 |
238 | - The server automatically manages sequence IDs for message exchanges
239 | - Authentication and session state are maintained for each client email
240 | - All API interactions are logged for debugging purposes
241 |
242 | ## Troubleshooting
243 |
244 | If you encounter issues:
245 |
246 | 1. **Authentication failures**: Verify your Connected App settings and ensure the client ID and secret are correct
247 | 2. **Session creation errors**: Check your Agent ID and make sure it's the 18-character version
248 | 3. **Connection issues**: Verify your Salesforce My Domain URL is correct (without "https://" prefix)
249 | 4. **Permission errors**: Make sure your Connected App has the proper OAuth scopes enabled
250 |
251 | ## Testing the Setup
252 |
253 | You can test your setup using the included test script:
254 |
255 | ```bash
256 | python test_agentforce.py
257 | ```
258 |
259 | This will attempt to authenticate, create a session, and exchange messages with your Agentforce agent.
260 |
261 | ## Contributing and GitHub Push
262 |
263 | This repository includes a helpful script that simplifies the process of pushing your changes to GitHub:
264 |
265 | ```bash
266 | chmod +x github_push.sh
267 | ./github_push.sh
268 | ```
269 |
270 | The `github_push.sh` script will:
271 |
272 | 1. Check if git is installed on your system
273 | 2. Verify that sensitive files like `.env` won't be pushed (they're in `.gitignore`)
274 | 3. Prompt you for your GitHub repository URL
275 | 4. Initialize a git repository if needed, or update the remote URL
276 | 5. Add all files and display them for your review
277 | 6. Commit the changes with a descriptive message
278 | 7. Push the changes to GitHub
279 |
280 | This makes it easy to share your customizations or contribute back to the project while ensuring that sensitive information stays secure.
281 |
```
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
```
1 | httpx>=0.24.0
2 | python-dotenv>=1.0.0
3 | mcp>=1.2.0
4 |
```
--------------------------------------------------------------------------------
/test_agentforce.py:
--------------------------------------------------------------------------------
```python
1 | #!/usr/bin/env python3
2 | import httpx
3 | import asyncio
4 | import os
5 | from dotenv import load_dotenv
6 | from agentforce_client import AgentforceClient
7 |
8 | async def test_agentforce_api():
9 | """Test the Agentforce API directly"""
10 | # Load environment variables
11 | load_dotenv()
12 |
13 | # Create client
14 | client = AgentforceClient(
15 | server_url=os.getenv("SALESFORCE_SERVER_URL"),
16 | client_id=os.getenv("SALESFORCE_CLIENT_ID"),
17 | client_secret=os.getenv("SALESFORCE_CLIENT_SECRET"),
18 | agent_id=os.getenv("SALESFORCE_AGENT_ID")
19 | )
20 |
21 | # Test email to use
22 | test_email = "[email protected]"
23 |
24 | print(f"Testing Agentforce API with {test_email}")
25 |
26 | # Step 1: Get access token
27 | print("\n1. Getting access token...")
28 | token_response = await client.get_access_token(test_email)
29 |
30 | if not token_response or not token_response.get('access_token'):
31 | print("❌ Failed to get access token")
32 | return
33 |
34 | print(f"✅ Got access token: {token_response['access_token'][:10]}...")
35 |
36 | # Step 2: Create session
37 | print("\n2. Creating session...")
38 | session_id = await client.create_session(
39 | token=token_response['access_token'],
40 | instance_url=token_response['instance_url']
41 | )
42 |
43 | if not session_id:
44 | print("❌ Failed to create session")
45 | return
46 |
47 | print(f"✅ Created session with ID: {session_id}")
48 |
49 | # Step 3: Send message
50 | print("\n3. Sending test message...")
51 | message_response = await client.send_message(
52 | session_id=session_id,
53 | token=token_response['access_token'],
54 | message="Hello, can you help me find a hotel near CDG airport?",
55 | sequence_id=1
56 | )
57 |
58 | print(f"✅ Got response: {message_response['agent_response'][:100]}...")
59 | print(f"Next sequence ID: {message_response['sequence_id']}")
60 |
61 | # Step 4: Send follow-up message
62 | print("\n4. Sending follow-up message...")
63 | followup_response = await client.send_message(
64 | session_id=session_id,
65 | token=token_response['access_token'],
66 | message="I need a room for 2 people for tonight",
67 | sequence_id=message_response['sequence_id']
68 | )
69 |
70 | print(f"✅ Got response: {followup_response['agent_response'][:100]}...")
71 |
72 | print("\nTest completed successfully!")
73 |
74 | if __name__ == "__main__":
75 | asyncio.run(test_agentforce_api())
76 |
```
--------------------------------------------------------------------------------
/github_push.sh:
--------------------------------------------------------------------------------
```bash
1 | #!/bin/bash
2 |
3 | # Colors for terminal output
4 | RED='\033[0;31m'
5 | GREEN='\033[0;32m'
6 | YELLOW='\033[1;33m'
7 | BLUE='\033[0;34m'
8 | NC='\033[0m' # No Color
9 |
10 | echo -e "${BLUE}=====================================================${NC}"
11 | echo -e "${BLUE} Agentforce MCP Server - GitHub Push Script ${NC}"
12 | echo -e "${BLUE}=====================================================${NC}"
13 |
14 | # Check if git is installed
15 | if ! command -v git &> /dev/null; then
16 | echo -e "${RED}Git is not installed. Please install git and try again.${NC}"
17 | exit 1
18 | fi
19 |
20 | # Get the current directory
21 | PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22 | cd "$PROJECT_DIR"
23 |
24 | # Check if .env file exists and warn the user
25 | if [ -f ".env" ]; then
26 | echo -e "${YELLOW}WARNING: .env file detected. This file contains sensitive information and should NOT be pushed to GitHub.${NC}"
27 | echo -e "${YELLOW}The .env file has been added to .gitignore, but please verify it won't be included in your commit.${NC}"
28 | echo ""
29 | fi
30 |
31 | # Ask for GitHub repository URL
32 | echo -e "${BLUE}Please enter your GitHub repository URL (e.g., https://github.com/username/repo.git):${NC}"
33 | read -r REPO_URL
34 |
35 | if [ -z "$REPO_URL" ]; then
36 | echo -e "${RED}No repository URL provided. Exiting.${NC}"
37 | exit 1
38 | fi
39 |
40 | # Check if the directory is already a git repository
41 | if [ ! -d ".git" ]; then
42 | echo -e "${YELLOW}Initializing Git repository...${NC}"
43 | git init
44 | echo -e "${GREEN}Git repository initialized.${NC}"
45 | else
46 | echo -e "${GREEN}Git repository already exists.${NC}"
47 | fi
48 |
49 | # Check if remote already exists
50 | if git remote | grep -q "origin"; then
51 | echo -e "${YELLOW}Remote 'origin' already exists. Updating to new URL...${NC}"
52 | git remote set-url origin "$REPO_URL"
53 | else
54 | echo -e "${YELLOW}Adding remote 'origin'...${NC}"
55 | git remote add origin "$REPO_URL"
56 | fi
57 |
58 | echo -e "${GREEN}Remote 'origin' set to: $REPO_URL${NC}"
59 |
60 | # Add all files to git
61 | echo -e "${YELLOW}Adding files to git...${NC}"
62 | git add .
63 |
64 | # Show status
65 | echo -e "${YELLOW}Git status:${NC}"
66 | git status
67 |
68 | # Confirm with user before committing
69 | echo -e "${BLUE}Review the files above. Are you sure you want to commit these files? (y/n)${NC}"
70 | read -r CONFIRM
71 |
72 | if [ "$CONFIRM" != "y" ] && [ "$CONFIRM" != "Y" ]; then
73 | echo -e "${RED}Commit aborted by user.${NC}"
74 | exit 1
75 | fi
76 |
77 | # Commit
78 | echo -e "${YELLOW}Committing changes...${NC}"
79 | git commit -m "Initial commit of Agentforce MCP Server"
80 |
81 | # Push to GitHub
82 | echo -e "${YELLOW}Pushing to GitHub...${NC}"
83 | git push -u origin master || git push -u origin main
84 |
85 | # Final message
86 | echo -e "${GREEN}Push completed! Your code should now be on GitHub.${NC}"
87 | echo -e "${BLUE}=====================================================${NC}"
88 | echo -e "${YELLOW}Next steps:${NC}"
89 | echo -e "1. Ensure your .env file is not published (it should be in .gitignore)"
90 | echo -e "2. Share the .env.example file with users so they know what environment variables to set"
91 | echo -e "3. Update the README.md if needed with specific instructions for your repository"
92 | echo -e "${BLUE}=====================================================${NC}"
93 |
```
--------------------------------------------------------------------------------
/session_manager.py:
--------------------------------------------------------------------------------
```python
1 | from typing import Dict, Any, Optional
2 | import logging
3 |
4 | logger = logging.getLogger(__name__)
5 |
6 | class SessionManager:
7 | """Manages Agentforce sessions and sequence IDs"""
8 |
9 | def __init__(self):
10 | self.sessions: Dict[str, Dict[str, Any]] = {}
11 |
12 | def store_auth_info(self, client_email: str, token_info: Dict[str, str]) -> None:
13 | """Store authentication information for a client"""
14 | self.sessions[client_email] = {
15 | 'access_token': token_info.get('access_token'),
16 | 'instance_url': token_info.get('instance_url'),
17 | 'session_id': None,
18 | 'last_sequence_id': 0
19 | }
20 |
21 | def store_session_id(self, client_email: str, session_id: str) -> None:
22 | """Store session ID for a client"""
23 | if client_email in self.sessions:
24 | self.sessions[client_email]['session_id'] = session_id
25 | self.sessions[client_email]['last_sequence_id'] = 0
26 | else:
27 | logger.error(f"❌ Client email {client_email} not found in sessions")
28 |
29 | def update_sequence_id(self, client_email: str, sequence_id: int) -> None:
30 | """Update sequence ID for a client"""
31 | if client_email in self.sessions:
32 | self.sessions[client_email]['last_sequence_id'] = sequence_id
33 | else:
34 | logger.error(f"❌ Client email {client_email} not found in sessions")
35 |
36 | def get_next_sequence_id(self, client_email: str) -> int:
37 | """Get next sequence ID for a client"""
38 | if client_email in self.sessions:
39 | return self.sessions[client_email]['last_sequence_id'] + 1
40 | else:
41 | logger.error(f"❌ Client email {client_email} not found in sessions")
42 | return 1 # Default to 1 if not found
43 |
44 | def get_access_token(self, client_email: str) -> Optional[str]:
45 | """Get access token for a client"""
46 | if client_email in self.sessions:
47 | return self.sessions[client_email].get('access_token')
48 | else:
49 | logger.error(f"❌ Client email {client_email} not found in sessions")
50 | return None
51 |
52 | def get_instance_url(self, client_email: str) -> Optional[str]:
53 | """Get instance URL for a client"""
54 | if client_email in self.sessions:
55 | return self.sessions[client_email].get('instance_url')
56 | else:
57 | logger.error(f"❌ Client email {client_email} not found in sessions")
58 | return None
59 |
60 | def get_session_id(self, client_email: str) -> Optional[str]:
61 | """Get session ID for a client"""
62 | if client_email in self.sessions:
63 | return self.sessions[client_email].get('session_id')
64 | else:
65 | logger.error(f"❌ Client email {client_email} not found in sessions")
66 | return None
67 |
68 | def get_session_status(self, client_email: str) -> str:
69 | """Get session status for a client"""
70 | if client_email not in self.sessions:
71 | return "No active session. You need to authenticate first."
72 |
73 | session_info = self.sessions[client_email]
74 |
75 | status = f"Client Email: {client_email}\n"
76 | status += f"Authenticated: {'Yes' if session_info.get('access_token') else 'No'}\n"
77 | status += f"Session ID: {session_info.get('session_id') or 'Not created'}\n"
78 | status += f"Last Sequence ID: {session_info.get('last_sequence_id', 0)}\n"
79 |
80 | return status
81 |
82 | def is_authenticated(self, client_email: str) -> bool:
83 | """Check if a client is authenticated"""
84 | return client_email in self.sessions and self.sessions[client_email].get('access_token') is not None
85 |
86 | def has_session(self, client_email: str) -> bool:
87 | """Check if a client has a session"""
88 | return client_email in self.sessions and self.sessions[client_email].get('session_id') is not None
89 |
```
--------------------------------------------------------------------------------
/agentforce_mcp_server.py:
--------------------------------------------------------------------------------
```python
1 | #!/usr/bin/env python3
2 | from mcp.server.fastmcp import FastMCP
3 | from typing import Dict, Optional, List, Any
4 | import os
5 | import logging
6 | from dotenv import load_dotenv
7 | from agentforce_client import AgentforceClient
8 | from session_manager import SessionManager
9 |
10 | # Configure logging
11 | logging.basicConfig(
12 | level=logging.INFO,
13 | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
14 | )
15 | logger = logging.getLogger(__name__)
16 |
17 | # Load environment variables from .env file
18 | load_dotenv()
19 |
20 | # Initialize FastMCP server
21 | mcp = FastMCP("agentforce-mcp-server")
22 |
23 | # Initialize the Agentforce client
24 | agentforce_client = AgentforceClient(
25 | server_url=os.getenv("SALESFORCE_SERVER_URL"),
26 | client_id=os.getenv("SALESFORCE_CLIENT_ID"),
27 | client_secret=os.getenv("SALESFORCE_CLIENT_SECRET"),
28 | agent_id=os.getenv("SALESFORCE_AGENT_ID")
29 | )
30 |
31 | # Initialize the session manager
32 | session_manager = SessionManager()
33 |
34 | @mcp.tool()
35 | async def authenticate(client_email: str) -> str:
36 | """Authenticate with the Agentforce API and store the token.
37 |
38 | Args:
39 | client_email: Email of the client for authentication
40 | """
41 | token_response = await agentforce_client.get_access_token(client_email)
42 |
43 | if not token_response or not token_response.get('access_token'):
44 | return "Failed to authenticate. Please check the client email and try again."
45 |
46 | # Store token information
47 | session_manager.store_auth_info(client_email, token_response)
48 |
49 | return f"Successfully authenticated as {client_email}"
50 |
51 | @mcp.tool()
52 | async def create_agent_session(client_email: str) -> str:
53 | """Create a session with an Agentforce agent.
54 |
55 | Args:
56 | client_email: Email of the authenticated client
57 | """
58 | if not session_manager.is_authenticated(client_email):
59 | return "You need to authenticate first using the authenticate tool."
60 |
61 | token = session_manager.get_access_token(client_email)
62 | instance_url = session_manager.get_instance_url(client_email)
63 |
64 | session_id = await agentforce_client.create_session(token, instance_url)
65 |
66 | if not session_id:
67 | return "Failed to create session. Please try again."
68 |
69 | # Store session ID
70 | session_manager.store_session_id(client_email, session_id)
71 |
72 | return f"Successfully created session with agent. Session ID: {session_id}"
73 |
74 | @mcp.tool()
75 | async def send_message_to_agent(client_email: str, message: str) -> str:
76 | """Send a message to the Agentforce agent and get the response.
77 |
78 | Args:
79 | client_email: Email of the authenticated client
80 | message: Message to send to the agent
81 | """
82 | if not session_manager.is_authenticated(client_email):
83 | return "You need to authenticate first using the authenticate tool."
84 |
85 | if not session_manager.has_session(client_email):
86 | return "You need to create a session first using the create_agent_session tool."
87 |
88 | token = session_manager.get_access_token(client_email)
89 | session_id = session_manager.get_session_id(client_email)
90 | next_sequence_id = session_manager.get_next_sequence_id(client_email)
91 |
92 | response = await agentforce_client.send_message(
93 | session_id=session_id,
94 | token=token,
95 | message=message,
96 | sequence_id=next_sequence_id
97 | )
98 |
99 | # Update last sequence ID
100 | session_manager.update_sequence_id(client_email, response['sequence_id'])
101 |
102 | return response['agent_response']
103 |
104 | @mcp.tool()
105 | async def get_session_status(client_email: str) -> str:
106 | """Get the status of the current session.
107 |
108 | Args:
109 | client_email: Email of the authenticated client
110 | """
111 | return session_manager.get_session_status(client_email)
112 |
113 | @mcp.tool()
114 | async def complete_agentforce_conversation(client_email: str, user_query: str) -> str:
115 | """Complete full conversation flow with Agentforce - authenticate, create session, and send message.
116 |
117 | Args:
118 | client_email: Email of the client for authentication
119 | user_query: Message to send to the agent
120 | """
121 | # Step 1: Authenticate
122 | if not session_manager.is_authenticated(client_email):
123 | auth_result = await authenticate(client_email)
124 | if not "Successfully" in auth_result:
125 | return f"Authentication failed: {auth_result}"
126 | logger.info(f"Authentication successful for {client_email}")
127 |
128 | # Step 2: Create session
129 | if not session_manager.has_session(client_email):
130 | session_result = await create_agent_session(client_email)
131 | if not "Successfully" in session_result:
132 | return f"Session creation failed: {session_result}"
133 | logger.info("Session created successfully")
134 |
135 | # Step 3: Send message
136 | response = await send_message_to_agent(client_email, user_query)
137 | return response
138 |
139 | if __name__ == "__main__":
140 | logger.info("Starting Agentforce MCP Server...")
141 | logger.info(f"Using Agent ID: {os.getenv('SALESFORCE_AGENT_ID')}")
142 |
143 | # Initialize and run the server
144 | mcp.run(transport='stdio')
145 |
```
--------------------------------------------------------------------------------
/agentforce_client.py:
--------------------------------------------------------------------------------
```python
1 | import httpx
2 | import json
3 | import logging
4 | import uuid
5 | from typing import Dict, Any, Optional
6 |
7 | logging.basicConfig(level=logging.INFO)
8 | logger = logging.getLogger(__name__)
9 |
10 | class AgentforceClient:
11 | """Client for interacting with the Salesforce Agentforce API"""
12 |
13 | def __init__(self, server_url: str, client_id: str, client_secret: str, agent_id: str):
14 | self.server_url = server_url
15 | self.client_id = client_id
16 | self.client_secret = client_secret
17 | self.agent_id = agent_id
18 | self.token_url = f"https://{server_url}/services/oauth2/token"
19 | self.api_url = "https://api.salesforce.com"
20 |
21 | async def get_access_token(self, client_email: str) -> Optional[Dict[str, str]]:
22 | """Get an access token for the Agentforce API"""
23 | try:
24 | payload = {
25 | 'grant_type': 'client_credentials',
26 | 'client_id': self.client_id,
27 | 'client_secret': self.client_secret,
28 | 'client_email': client_email
29 | }
30 |
31 | headers = {
32 | 'Content-Type': 'application/x-www-form-urlencoded'
33 | }
34 |
35 | async with httpx.AsyncClient() as client:
36 | response = await client.post(self.token_url, data=payload, headers=headers)
37 | response.raise_for_status()
38 |
39 | token_data = response.json()
40 | logger.info(f"✅ Token retrieved for {client_email}")
41 |
42 | return {
43 | 'client_email': client_email,
44 | 'access_token': token_data.get('access_token'),
45 | 'instance_url': token_data.get('instance_url')
46 | }
47 |
48 | except Exception as e:
49 | logger.error(f"❌ Error retrieving token: {str(e)}")
50 | return None
51 |
52 | async def create_session(self, token: str, instance_url: str) -> Optional[str]:
53 | """Create a session with the Agentforce API"""
54 | try:
55 | session_url = f"{self.api_url}/einstein/ai-agent/v1/agents/{self.agent_id}/sessions"
56 |
57 | random_uuid = str(uuid.uuid4())
58 |
59 | payload = {
60 | 'externalSessionKey': random_uuid,
61 | 'instanceConfig': {
62 | 'endpoint': instance_url
63 | },
64 | 'streamingCapabilities': {
65 | 'chunkTypes': ['Text']
66 | },
67 | 'bypassUser': True
68 | }
69 |
70 | headers = {
71 | 'Authorization': f'Bearer {token}',
72 | 'Content-Type': 'application/json'
73 | }
74 |
75 | async with httpx.AsyncClient() as client:
76 | response = await client.post(session_url, json=payload, headers=headers)
77 | response.raise_for_status()
78 |
79 | session_data = response.json()
80 | session_id = session_data.get('sessionId')
81 |
82 | if session_id:
83 | logger.info(f"✅ Session Created, ID: {session_id}")
84 | return session_id
85 | else:
86 | logger.error("❌ No session ID in response")
87 | return None
88 |
89 | except Exception as e:
90 | logger.error(f"❌ Error creating session: {str(e)}")
91 | return None
92 |
93 | async def send_message(self, session_id: str, token: str, message: str, sequence_id: int = 1) -> Dict[str, Any]:
94 | """Send a message to the Agentforce API"""
95 | try:
96 | message_url = f"{self.api_url}/einstein/ai-agent/v1/sessions/{session_id}/messages"
97 |
98 | payload = {
99 | 'message': {
100 | 'sequenceId': sequence_id,
101 | 'type': 'Text',
102 | 'text': message
103 | }
104 | }
105 |
106 | headers = {
107 | 'Authorization': f'Bearer {token}',
108 | 'Content-Type': 'application/json'
109 | }
110 |
111 | logger.info(f"📤 Sending request with body: {json.dumps(payload)}")
112 |
113 | async with httpx.AsyncClient() as client:
114 | response = await client.post(message_url, json=payload, headers=headers, timeout=120.0)
115 | response.raise_for_status()
116 |
117 | response_data = response.json()
118 |
119 | result = {
120 | 'session_id': session_id,
121 | 'sequence_id': sequence_id + 1, # Increment sequence ID for next message
122 | 'agent_response': None
123 | }
124 |
125 | if 'messages' in response_data and response_data['messages']:
126 | result['agent_response'] = response_data['messages'][0].get('message')
127 | else:
128 | result['agent_response'] = 'No response message received'
129 |
130 | return result
131 |
132 | except Exception as e:
133 | logger.error(f"❌ Error sending message: {str(e)}")
134 | return {
135 | 'session_id': session_id,
136 | 'sequence_id': sequence_id,
137 | 'agent_response': f"Error sending message: {str(e)}"
138 | }
139 |
```
--------------------------------------------------------------------------------
/setup.sh:
--------------------------------------------------------------------------------
```bash
1 | #!/bin/bash
2 |
3 | # Colors for terminal output
4 | RED='\033[0;31m'
5 | GREEN='\033[0;32m'
6 | YELLOW='\033[1;33m'
7 | BLUE='\033[0;34m'
8 | CYAN='\033[0;36m'
9 | NC='\033[0m' # No Color
10 |
11 | # Get the absolute path of the installation directory
12 | INSTALL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13 | echo "Installation directory: $INSTALL_DIR"
14 |
15 | clear
16 | echo -e "${BLUE}======================================================${NC}"
17 | echo -e "${BLUE} Agentforce MCP Server - Setup Script ${NC}"
18 | echo -e "${BLUE}======================================================${NC}"
19 | echo
20 |
21 | # Update Homebrew if it exists
22 | if command -v brew &> /dev/null; then
23 | echo -e "${YELLOW}Updating Homebrew...${NC}"
24 | brew update
25 | echo -e "${GREEN}Homebrew updated successfully.${NC}"
26 |
27 | # Check if Python is installed via Homebrew
28 | if ! brew list [email protected] &> /dev/null; then
29 | echo -e "${YELLOW}Installing Python 3.10 via Homebrew...${NC}"
30 | brew install [email protected]
31 | echo -e "${GREEN}Python 3.10 installed successfully.${NC}"
32 | else
33 | echo -e "${GREEN}Python 3.10 is already installed.${NC}"
34 | fi
35 | fi
36 |
37 | # Check Python version
38 | echo -e "${YELLOW}Checking Python version...${NC}"
39 | if command -v python3 &> /dev/null; then
40 | PYTHON_CMD="python3"
41 | elif command -v python &> /dev/null; then
42 | PYTHON_CMD="python"
43 | else
44 | echo -e "${RED}Python not found. Please install Python 3.10 or higher and try again.${NC}"
45 | exit 1
46 | fi
47 |
48 | # Get Python version
49 | PYTHON_VERSION=$($PYTHON_CMD --version | cut -d " " -f 2)
50 | PYTHON_MAJOR=$(echo $PYTHON_VERSION | cut -d. -f1)
51 | PYTHON_MINOR=$(echo $PYTHON_VERSION | cut -d. -f2)
52 |
53 | echo -e "${GREEN}Found Python $PYTHON_VERSION${NC}"
54 |
55 | # Check Python version is at least 3.10
56 | if [ "$PYTHON_MAJOR" -lt 3 ] || ([ "$PYTHON_MAJOR" -eq 3 ] && [ "$PYTHON_MINOR" -lt 10 ]); then
57 | echo -e "${RED}Python 3.10 or higher is required. You have Python $PYTHON_VERSION.${NC}"
58 | echo -e "${RED}Please install a newer version of Python and try again.${NC}"
59 | exit 1
60 | fi
61 |
62 | # Create and activate virtual environment
63 | VENV_DIR="$INSTALL_DIR/.venv"
64 | echo -e "${YELLOW}Creating virtual environment in $VENV_DIR...${NC}"
65 |
66 | # Remove existing venv if it exists
67 | if [ -d "$VENV_DIR" ]; then
68 | echo -e "${YELLOW}Removing existing virtual environment...${NC}"
69 | rm -rf "$VENV_DIR"
70 | fi
71 |
72 | # Create a new virtual environment
73 | $PYTHON_CMD -m venv "$VENV_DIR"
74 | if [ $? -ne 0 ]; then
75 | echo -e "${RED}Failed to create virtual environment. Please check if you have venv module installed.${NC}"
76 | echo -e "${YELLOW}You can install it with: pip install --user virtualenv${NC}"
77 | exit 1
78 | fi
79 |
80 | echo -e "${GREEN}Virtual environment created successfully.${NC}"
81 |
82 | # Activate the virtual environment
83 | echo -e "${YELLOW}Activating virtual environment...${NC}"
84 | source "$VENV_DIR/bin/activate"
85 | if [ $? -ne 0 ]; then
86 | echo -e "${RED}Failed to activate virtual environment.${NC}"
87 | exit 1
88 | fi
89 |
90 | echo -e "${GREEN}Virtual environment activated successfully.${NC}"
91 |
92 | # Upgrade pip in the virtual environment
93 | echo -e "${YELLOW}Upgrading pip...${NC}"
94 | pip install --upgrade pip
95 | if [ $? -ne 0 ]; then
96 | echo -e "${RED}Failed to upgrade pip. Continuing anyway...${NC}"
97 | fi
98 |
99 | # Install dependencies
100 | echo -e "${YELLOW}Installing required dependencies...${NC}"
101 | pip install -r "$INSTALL_DIR/requirements.txt"
102 | if [ $? -ne 0 ]; then
103 | echo -e "${RED}Failed to install dependencies. Please check the error message above and try again.${NC}"
104 | exit 1
105 | fi
106 | echo -e "${GREEN}Dependencies installed successfully.${NC}"
107 |
108 | # Make the server script executable
109 | chmod +x "$INSTALL_DIR/agentforce_mcp_server.py"
110 |
111 | # Check if .env file already exists
112 | if [ -f "$INSTALL_DIR/.env" ]; then
113 | echo -e "${YELLOW}An .env file already exists.${NC}"
114 | read -p "Do you want to overwrite it? (y/n): " -n 1 -r
115 | echo
116 | if [[ ! $REPLY =~ ^[Yy]$ ]]; then
117 | echo -e "${YELLOW}Keeping existing .env file.${NC}"
118 | echo -e "${YELLOW}If you encounter issues, you can run this script again to recreate the file.${NC}"
119 | else
120 | # We'll create a new .env file below
121 | echo -e "${YELLOW}Creating new .env file...${NC}"
122 | fi
123 | else
124 | # Create .env file
125 | echo -e "${YELLOW}Setting up your environment variables...${NC}"
126 | fi
127 |
128 | if [[ ! -f "$INSTALL_DIR/.env" || $REPLY =~ ^[Yy]$ ]]; then
129 | # Get Salesforce credentials
130 | echo -e "${CYAN}Please enter your Salesforce credentials:${NC}"
131 | echo -e "${YELLOW}(Press Enter to skip any field and use empty value)${NC}"
132 |
133 | read -p "Salesforce Org ID: " ORG_ID
134 | read -p "Salesforce Agent ID: " AGENT_ID
135 | read -p "Salesforce Client ID (Consumer Key): " CLIENT_ID
136 | read -p "Salesforce Client Secret (Consumer Secret): " CLIENT_SECRET
137 | read -p "Salesforce Server URL (e.g., example.my.salesforce.com): " SERVER_URL
138 |
139 | # Create .env file
140 | cat > "$INSTALL_DIR/.env" << EOF
141 | SALESFORCE_ORG_ID="${ORG_ID}"
142 | SALESFORCE_AGENT_ID="${AGENT_ID}"
143 | SALESFORCE_CLIENT_ID="${CLIENT_ID}"
144 | SALESFORCE_CLIENT_SECRET="${CLIENT_SECRET}"
145 | SALESFORCE_SERVER_URL="${SERVER_URL}"
146 | EOF
147 |
148 | echo -e "${GREEN}.env file created successfully.${NC}"
149 | fi
150 |
151 | # Check if we want to test the setup
152 | echo
153 | read -p "Do you want to test the setup now? (y/n): " -n 1 -r TEST_SETUP
154 | echo
155 |
156 | if [[ $TEST_SETUP =~ ^[Yy]$ ]]; then
157 | echo -e "${YELLOW}Testing Agentforce connection...${NC}"
158 | echo -e "${YELLOW}This will attempt to authenticate and create a session.${NC}"
159 |
160 | python "$INSTALL_DIR/test_agentforce.py"
161 |
162 | if [ $? -ne 0 ]; then
163 | echo -e "${RED}Test failed. Please check your credentials and try again.${NC}"
164 | else
165 | echo -e "${GREEN}Test completed. Your setup is working!${NC}"
166 | fi
167 | fi
168 |
169 | # Determine the MCP configuration paths based on OS
170 | PYTHON_PATH="$VENV_DIR/bin/python"
171 | SERVER_SCRIPT="$INSTALL_DIR/agentforce_mcp_server.py"
172 |
173 | # For Windows, adjust paths
174 | if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
175 | # Convert paths to Windows format
176 | PYTHON_PATH=$(echo "$PYTHON_PATH" | sed 's/\//\\/g')
177 | SERVER_SCRIPT=$(echo "$SERVER_SCRIPT" | sed 's/\//\\/g')
178 | fi
179 |
180 | # Generate the Claude Desktop configuration JSON
181 | CONFIG_JSON=$(cat << EOF
182 | {
183 | "mcpServers": {
184 | "agentforce": {
185 | "command": "$PYTHON_PATH",
186 | "args": [
187 | "$SERVER_SCRIPT"
188 | ]
189 | }
190 | }
191 | }
192 | EOF
193 | )
194 |
195 | # Check if we want to start the server
196 | echo
197 | read -p "Do you want to start the MCP server now? (y/n): " -n 1 -r START_SERVER
198 | echo
199 |
200 | if [[ $START_SERVER =~ ^[Yy]$ ]]; then
201 | echo -e "${YELLOW}Starting Agentforce MCP Server...${NC}"
202 | echo -e "${YELLOW}Press Ctrl+C to stop the server.${NC}"
203 |
204 | python "$INSTALL_DIR/agentforce_mcp_server.py"
205 | else
206 | # Determine OS-specific config path
207 | if [[ "$OSTYPE" == "darwin"* ]]; then
208 | CONFIG_PATH="$HOME/Library/Application Support/Claude/claude_desktop_config.json"
209 | CONFIG_DIR="$HOME/Library/Application Support/Claude"
210 | elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
211 | CONFIG_PATH="$APPDATA\\Claude\\claude_desktop_config.json"
212 | CONFIG_DIR="$APPDATA\\Claude"
213 | else
214 | # Linux or other
215 | CONFIG_PATH="$HOME/.config/Claude/claude_desktop_config.json"
216 | CONFIG_DIR="$HOME/.config/Claude"
217 | fi
218 |
219 | # Provide instructions for setting up Claude Desktop
220 | echo -e "${BLUE}======================================================${NC}"
221 | echo -e "${BLUE} Claude Desktop Configuration Guide ${NC}"
222 | echo -e "${BLUE}======================================================${NC}"
223 | echo
224 | echo -e "${CYAN}To use this server with Claude Desktop:${NC}"
225 | echo
226 | echo -e "1. Locate your Claude Desktop configuration file:"
227 | echo -e " ${YELLOW}macOS:${NC} ~/Library/Application Support/Claude/claude_desktop_config.json"
228 | echo -e " ${YELLOW}Windows:${NC} %APPDATA%\\Claude\\claude_desktop_config.json"
229 | echo
230 | echo -e "2. Add the following to your configuration:"
231 | echo
232 | echo -e "${YELLOW}"
233 | echo "$CONFIG_JSON" | jq .
234 | echo -e "${NC}"
235 | echo
236 |
237 | # Ask if user wants to automatically update the config
238 | read -p "Do you want to automatically update your Claude Desktop configuration? (y/n): " -n 1 -r AUTO_CONFIG
239 | echo
240 |
241 | if [[ $AUTO_CONFIG =~ ^[Yy]$ ]]; then
242 | # Create config directory if it doesn't exist
243 | mkdir -p "$CONFIG_DIR"
244 |
245 | # Check if config file exists
246 | if [ -f "$CONFIG_PATH" ]; then
247 | # Backup existing config
248 | cp "$CONFIG_PATH" "${CONFIG_PATH}.backup"
249 | echo -e "${YELLOW}Existing config backed up to ${CONFIG_PATH}.backup${NC}"
250 |
251 | # Update existing config
252 | if command -v jq &> /dev/null; then
253 | # Use jq to merge configs if available
254 | jq -s '.[0] * .[1]' "$CONFIG_PATH" <(echo "$CONFIG_JSON") > "${CONFIG_PATH}.tmp"
255 | mv "${CONFIG_PATH}.tmp" "$CONFIG_PATH"
256 | else
257 | # Simple replacement if jq is not available
258 | echo "$CONFIG_JSON" > "$CONFIG_PATH"
259 | fi
260 | else
261 | # Create new config file
262 | echo "$CONFIG_JSON" > "$CONFIG_PATH"
263 | fi
264 |
265 | echo -e "${GREEN}Claude Desktop configuration updated successfully.${NC}"
266 | echo -e "${YELLOW}Please restart Claude Desktop to apply the changes.${NC}"
267 | fi
268 |
269 | echo -e "3. Restart Claude Desktop"
270 | echo
271 | echo -e "4. Start a conversation with Claude and look for the hammer icon in the input box"
272 | echo -e " This indicates that MCP tools are available."
273 | echo
274 | echo -e "${GREEN}You can start the server at any time by running:${NC}"
275 | echo -e "${YELLOW}cd $INSTALL_DIR && source .venv/bin/activate && python agentforce_mcp_server.py${NC}"
276 | echo
277 | echo -e "${BLUE}======================================================${NC}"
278 | fi
279 |
280 | echo -e "\n${GREEN}Setup complete!${NC}"
281 |
282 | # Deactivate virtual environment if we're not starting the server
283 | if [[ ! $START_SERVER =~ ^[Yy]$ ]]; then
284 | deactivate 2>/dev/null || true
285 | fi
286 |
```