Batteries Plus MCP Demo Server
by sinisterace
README.md
# Batteries Plus MCP Demo Server
A production-ready [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for the **Batteries Plus Service Workflow** demo. This server connects **Salesforce Agentforce** agents to Batteries Plus product catalog, store inventory, and service handoff capabilities.
## What This Server Does
This MCP server enables Salesforce Agentforce agents to:
1. **Find Compatible Replacement Batteries** — Match customer vehicles to the right battery using product fit logic
2. **Check Store Availability** — Find nearby stores with inventory and service capabilities
3. **Create Store Visit Summaries** — Generate service handoffs for in-store battery testing and installation
**Demo Story:** A customer's car won't start. The agent identifies the right replacement battery, checks local availability, and creates a store visit summary—all without human handoff.
### What This Server Does NOT Do
This is a **service workflow demo**, not a transactional system. It explicitly does NOT:
- Create orders, reservations, or POS transactions
- Process payments or checkout flows
- Take employee-attributed actions
## Architecture
- **MCP SDK:** `@modelcontextprotocol/sdk` v1.0.4
- **Transport:** Streamable HTTP (recommended for remote MCP servers)
- **Runtime:** Node.js 20+ with TypeScript
- **Authentication:** OAuth 2.0 Client Credentials OR `none` mode for fast testing
- **Data Storage:** In-memory (optionally persisted to `/tmp` for handoffs)
- **Deployment:** Render.com (free tier)
## Project Structure
```
batteries-plus-mcp/
├── src/
│ ├── server.ts # Express + MCP SDK + tool registration
│ ├── auth.ts # OAuth 2.0 implementation
│ ├── data/
│ │ ├── batteries.ts # Vehicle fit database
│ │ └── stores.ts # Store + inventory database
│ ├── services/
│ │ ├── handoffStore.ts # In-memory handoff storage
│ │ └── salesforceWriter.ts # Placeholder for Salesforce integration
│ ├── tools/
│ │ ├── findCompatibleReplacement.ts # MCP tool 1
│ │ ├── checkStoreAvailability.ts # MCP tool 2
│ │ └── createStoreHandoff.ts # MCP tool 3
│ └── utils/
│ ├── ids.ts # Handoff ID generation
│ └── validation.ts # Input validation utilities
├── test/
│ └── smoke.test.js # Basic health checks
├── package.json
├── tsconfig.json
├── render.yaml # Render deployment config
└── .env.example # Environment template
```
## Quick Start
### Prerequisites
- Node.js 20+
- npm 9+
### Local Development
1. **Clone and Install**
```bash
cd batteries-plus-mcp
npm install
```
2. **Configure Environment**
```bash
cp .env.example .env
```
For fast local testing, use `MCP_AUTH_MODE=none`:
```env
PORT=3000
NODE_ENV=development
MCP_AUTH_MODE=none
WRITE_MODE=memory
```
3. **Start Dev Server**
```bash
npm run dev
```
The server starts at `http://localhost:3000` with hot-reload enabled.
4. **Verify Health**
```bash
curl http://localhost:3000/health
```
Expected response:
```json
{
"status": "healthy",
"service": "batteries-plus-service-mcp",
"version": "1.0.0",
"auth_mode": "none",
"write_mode": "memory",
"uptime_seconds": 12.4,
"handoffs_created": 0
}
```
### Production Build
```bash
npm run build # Compile TypeScript to ./dist
npm start # Run compiled server
```
## Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
| `PORT` | No | `3000` | Server port |
| `NODE_ENV` | No | `development` | Runtime environment |
| `MCP_AUTH_MODE` | No | `none` | Auth mode: `none` or `oauth` |
| `MCP_CLIENT_ID` | OAuth only | — | OAuth client ID |
| `MCP_CLIENT_SECRET` | OAuth only | — | OAuth client secret |
| `MCP_TOKEN_TTL_SECONDS` | No | `3600` | Token lifetime (1 hour) |
| `WRITE_MODE` | No | `memory` | Handoff storage: `memory` or `salesforce` |
**For Render deployment**, set `MCP_AUTH_MODE=oauth` and configure `MCP_CLIENT_ID` and `MCP_CLIENT_SECRET` in the Render dashboard.
## API Endpoints
### GET /health
Health check endpoint. Returns server status, auth mode, uptime, and handoff count.
### POST /oauth/token
OAuth 2.0 token endpoint (only active when `MCP_AUTH_MODE=oauth`).
**Request:**
```bash
curl -X POST https://your-server.onrender.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"
```
**Response:**
```json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "mcp.tools"
}
```
### POST /mcp
MCP protocol endpoint. Agentforce sends tool requests here via Streamable HTTP transport.
### GET /demo/handoffs
View all store visit summaries created by the agent. Returns HTML in browsers, JSON for API clients.
## MCP Tools
### 1. find_compatible_replacement
Finds a compatible replacement battery for a customer's vehicle.
**Input:**
```json
{
"vehicle_year": 2019,
"vehicle_make": "Honda",
"vehicle_model": "CR-V",
"vehicle_engine": "1.5L L4",
"postal_code": "53202"
}
```
**Output:**
```json
{
"fit_status": "compatible",
"fit_confidence": "high",
"vehicle": {
"year": 2019,
"make": "Honda",
"model": "CR-V",
"engine": "1.5L L4"
},
"recommended_product": {
"sku": "SLI51RAGM",
"name": "X2Power Premium AGM Battery - Group 51R",
"group_size": "51R",
"cold_cranking_amps": 500,
"reserve_capacity_minutes": 90,
"warranty_months": 36,
"install_eligible": true
},
"recommendation_reason": "This battery matches the vehicle's group size and cold-cranking amp requirements."
}
```
### 2. check_store_availability
Checks nearby Batteries Plus stores for a specific SKU.
**Input:**
```json
{
"sku": "SLI51RAGM",
"postal_code": "53202",
"radius_miles": 15
}
```
**Output:**
```json
{
"sku": "SLI51RAGM",
"postal_code": "53202",
"availability_status": "available_nearby",
"stores": [
{
"store_id": "BP-MKE-001",
"store_name": "Batteries Plus - Milwaukee East",
"address": "2401 S. Kinnickinnic Ave, Milwaukee, WI 53207",
"distance_miles": 2.3,
"phone": "(414) 555-0101",
"today_hours": "8:00 AM - 7:00 PM",
"quantity_available": 4,
"service_context": {
"battery_testing_available": true,
"installation_available": true,
"estimated_service_time": "20-30 minutes",
"note": "Store can test the current battery before installation."
}
}
]
}
```
### 3. create_store_handoff
Creates a store visit summary after the customer selects a store. **This tool will block unless `customer_confirmed_store` is `true`.**
**Input:**
```json
{
"customer_name": "Jane Doe",
"customer_issue": "Car will not start",
"vehicle_year": 2019,
"vehicle_make": "Honda",
"vehicle_model": "CR-V",
"vehicle_engine": "1.5L L4",
"recommended_sku": "SLI51RAGM",
"recommended_product_name": "X2Power Premium AGM Battery - Group 51R",
"selected_store_id": "BP-MKE-001",
"selected_store_name": "Batteries Plus - Milwaukee East",
"availability_result": "4 units available",
"customer_confirmed_store": true
}
```
**Output:**
```json
{
"status": "created",
"handoff_id": "SVS-20260622-1430",
"created_at": "2026-06-22T14:30:12.456Z",
"service_workflow": "Store Visit Summary",
"summary": {
"customer_issue": "Car will not start",
"vehicle": "2019 Honda CR-V 1.5L L4",
"recommended_sku": "SLI51RAGM",
"recommended_product_name": "X2Power Premium AGM Battery - Group 51R",
"selected_store": "Batteries Plus - Milwaukee East",
"availability_result": "4 units available",
"store_note": "Customer may need battery testing before replacement. Store has installation available today."
},
"demo_record_url": "https://example.salesforce.com/lightning/r/Store_Visit_Summary__c/SVS-20260622-1430/view"
}
```
## Testing Locally
### Smoke Tests
```bash
npm test
```
Runs basic health checks on `/health` and `/demo/handoffs`.
### Manual Tool Testing
You can test MCP tools manually using `curl` (when `MCP_AUTH_MODE=none`):
**Example: List Tools**
```bash
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}'
```
**Example: Call Tool**
```bash
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "find_compatible_replacement",
"arguments": {
"vehicle_year": 2019,
"vehicle_make": "Honda",
"vehicle_model": "CR-V"
}
}
}'
```
## Deploying to Render
### Initial Setup
1. **Create Render Account**
- Sign up at [render.com](https://render.com)
- Connect your GitHub account
2. **Push to GitHub**
```bash
git init
git add .
git commit -m "Initial commit: Batteries Plus MCP server"
git remote add origin https://github.com/YOUR_USERNAME/batteries-plus-mcp.git
git push -u origin main
```
3. **Create Render Service**
- Dashboard → **New +** → **Web Service**
- Connect repository: `batteries-plus-mcp`
- Render auto-detects `render.yaml` ✅
4. **Configure Environment Variables** (in Render dashboard)
- `MCP_CLIENT_ID` = `batteries-plus-agentforce-client` (or your custom value)
- `MCP_CLIENT_SECRET` = *Generate a strong secret (e.g., `openssl rand -base64 32`)*
5. **Deploy**
- Render automatically builds and deploys
- Wait for "Live" status (2-3 minutes)
6. **Note Your MCP Endpoint URL**
Your server URL will be: `https://batteries-plus-mcp.onrender.com`
Your **MCP Endpoint URL** for Salesforce registration is:
```
https://batteries-plus-mcp.onrender.com/mcp
```
### Health Check
```bash
curl https://batteries-plus-mcp.onrender.com/health
```
### Render Free Tier Notes
- Server sleeps after 15 minutes of inactivity
- First request after sleep takes ~30-60 seconds to wake up
- Monthly limit: 750 hours (adequate for demos)
- For production, upgrade to paid tier for always-on service
## Salesforce Agentforce Setup
### Step 1: Authenticate Salesforce CLI
If you haven't already authenticated to your Salesforce org:
```bash
sf org login web --alias batteries-demo
```
Use your **admin credentials** (not the Agentforce agent user credentials).
### Step 2: Register the MCP Server in Agentforce Registry
**Manual steps** (you will do this in the Salesforce UI):
1. **Open Salesforce Setup**
- Login to your Salesforce org
- Setup → Quick Find → "Agentforce"
2. **Register MCP Server**
- Agentforce → **Agentforce Registry** → **MCP Servers** tab
- Click **Register MCP Server**
3. **Enter Connection Details**
| Field | Value |
|---|---|
| **MCP Server Name** | `Batteries Plus Service` |
| **MCP Endpoint URL** | `https://batteries-plus-mcp.onrender.com/mcp` |
| **Authorization Token URL** | `https://batteries-plus-mcp.onrender.com/oauth/token` |
| **Client ID** | `batteries-plus-agentforce-client` |
| **Client Secret** | *Your `MCP_CLIENT_SECRET` from Render* |
| **Scope** | `mcp.tools` |
Click **Save & Test Connection**. You should see "✅ Connection successful".
4. **Manage Tools**
- After saving, click **Manage Tools**
- You should see 3 tools:
- `find_compatible_replacement`
- `check_store_availability`
- `create_store_handoff`
- **Select all 3** and click **Allow and Continue**
- Click **Save**
### Step 3: Add Tools to Your Agent
1. **Open Agentforce Builder**
- Setup → Agentforce → **Agents**
- Select or create your agent (e.g., "Batteries Plus Agent")
2. **Add Actions**
- In the agent builder, go to **Actions & Instructions**
- Click **Add Action**
- Search for "batteries" or "compatible"
- Add all 3 MCP tools:
- `find_compatible_replacement`
- `check_store_availability`
- `create_store_handoff`
3. **Configure Agent Instructions**
Add this to your agent instructions:
```
You are the Batteries Plus Service Agent. You help customers find the right replacement battery, check local store availability, and create store visit summaries.
When a customer describes a vehicle issue (e.g., "car won't start"), follow this workflow:
1. Ask for vehicle details: year, make, model (and engine if possible)
2. Use find_compatible_replacement to identify the right battery
3. Ask for customer location (postal code)
4. Use check_store_availability to find nearby stores with inventory
5. Present store options with availability, distance, and services
6. IMPORTANT: Wait for the customer to confirm their store choice
7. Only after confirmation, use create_store_handoff with customer_confirmed_store=true
Do not create orders, reservations, or process payments. Your role is to prepare a service handoff for in-store battery testing and installation.
```
4. **Activate the Agent**
- Click **Activate** in the agent builder
- Choose your deployment channel (e.g., Salesforce Experience Cloud, Messaging for In-App and Web)
### Step 4: Test the Agent
**Test Conversation Script:**
> **Customer:** My car won't start and I think I need a new battery. I drive a 2019 Honda CR-V and I'm near 53202. Can you help me find the right one nearby?
**Expected Agent Behavior:**
1. Agent calls `find_compatible_replacement` → identifies `SLI51RAGM` (X2Power Premium AGM Battery - Group 51R)
2. Agent calls `check_store_availability` → finds stores near 53202
3. Agent presents: "I found the **X2Power Premium AGM Battery - Group 51R** for your 2019 Honda CR-V. The closest store with availability is **Batteries Plus - Milwaukee East** (2.3 miles away, 4 units in stock). They offer free battery testing and installation. Would you like me to create a store visit summary?"
4. Customer confirms: "Yes, that works for me."
5. Agent calls `create_store_handoff` with `customer_confirmed_store=true`
6. Agent responds: "Great! I've created a store visit summary (ID: SVS-20260622-1430). The store knows you're coming and can test your current battery before replacement. Installation takes 20-30 minutes."
### Troubleshooting
**Problem: MCP tools don't appear in Agentforce actions picker**
- Switch to **legacy Agentforce Builder** (toggle at top of screen)
- Verify that `Agentforce MCP Support Pilot` permission is enabled on your org
**Problem: Connection test fails with "401 Unauthorized"**
- Check that `MCP_CLIENT_ID` and `MCP_CLIENT_SECRET` match between Render and Salesforce
- Verify `MCP_AUTH_MODE=oauth` is set in Render environment
**Problem: Tools return empty or error responses**
- Check Render logs: Dashboard → `batteries-plus-mcp` → **Logs**
- Add debug header in Salesforce: Setup → Named Credentials → Custom Headers → `x-sfdc-mcp-debug` = `0x3f`
**Problem: Agent doesn't use MCP tools**
- Review agent instructions—ensure tools are mentioned in the workflow
- Check agent trace logs: Agent Builder → **Session History** → select test session
## Development Notes
### Adding New Vehicles
Edit [src/data/batteries.ts](src/data/batteries.ts) and add entries to the `vehicleFitDatabase` array:
```typescript
{
vehicle_year: 2022,
vehicle_make: 'Tesla',
vehicle_model: 'Model 3',
vehicle_engine: 'Electric',
recommended_sku: 'EV12V-AUX',
product_name: 'Auxiliary 12V Battery for Tesla',
group_size: 'Tesla OEM',
cold_cranking_amps: 340,
reserve_capacity_minutes: 50,
warranty_months: 24,
install_eligible: false
}
```
### Adding New Stores
Edit [src/data/stores.ts](src/data/stores.ts) and add entries to the `storeDatabase` array:
```typescript
{
store_id: 'BP-CHI-004',
store_name: 'Batteries Plus - Chicago Loop',
postal_codes: ['60601', '60602'],
address: '123 State St, Chicago, IL 60601',
distance_miles: 0.5,
phone: '(312) 555-0200',
today_hours: '9:00 AM - 6:00 PM',
services: ['Battery testing', 'Battery installation', 'Curbside pickup'],
inventory: {
'SLI51RAGM': 3,
'SLI24FAGM': 2,
'SLI35AGM': 5
}
}
```
### Enabling Salesforce Write Mode
**This is for future integration.** To write handoffs to Salesforce instead of memory:
1. Set `WRITE_MODE=salesforce` in `.env`
2. Add Salesforce credentials:
```env
SF_LOGIN_URL=https://login.salesforce.com
SF_CLIENT_ID=<connected_app_consumer_key>
SF_CLIENT_SECRET=<connected_app_consumer_secret>
SF_USERNAME=<integration_user_username>
SF_PASSWORD=<integration_user_password>
SF_INSTANCE_URL=https://yourorg.my.salesforce.com
```
3. Implement Salesforce REST API calls in [src/services/salesforceWriter.ts](src/services/salesforceWriter.ts)
Current implementation throws "not yet implemented" when `WRITE_MODE=salesforce`.
## Technical Details
### MCP Protocol
This server implements MCP via **Streamable HTTP transport** (recommended for remote servers). Agentforce sends JSON-RPC 2.0 requests to the `/mcp` endpoint using Server-Sent Events (SSE) for streaming responses.
**Supported Methods:**
- `tools/list` — Returns tool schemas
- `tools/call` — Executes a tool with provided arguments
### Authentication Flow
When `MCP_AUTH_MODE=oauth`:
1. Agentforce requests a token from `/oauth/token` using client credentials grant
2. Server validates `client_id` and `client_secret`
3. Server issues a JWT signed with `MCP_CLIENT_SECRET`
4. Agentforce includes `Authorization: Bearer <token>` on subsequent `/mcp` requests
5. Server validates JWT signature and checks issued token set
Tokens expire after `MCP_TOKEN_TTL_SECONDS` (default: 1 hour).
### Data Storage
- **Batteries Fit Database:** In-memory array in [src/data/batteries.ts](src/data/batteries.ts)
- **Store Database:** In-memory array in [src/data/stores.ts](src/data/stores.ts)
- **Handoff Storage:** In-memory Map with optional persistence to `/tmp/batteries-plus-handoffs.json`
For production, replace with:
- PostgreSQL or Snowflake for product/store data
- Redis for token management
- Salesforce REST API for handoff records
## License
MIT
## Support
For issues or questions about this demo server, contact:
- Alisha Christy (alishachisty@outlook.com)
- Open an issue in the GitHub repository
---
**Built with [Model Context Protocol](https://modelcontextprotocol.io) SDK v1.0.4**
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues