#
tokens: 12604/50000 11/11 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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 | 
```