# 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 | ```