Skip to main content
Glama
sinisterace

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**