# Consumer OAuth for Custom MCP Servers
Configure per-user OAuth authentication so your AI agent's end-users can connect their own accounts to third-party services
Consumer OAuth allows each end-user of your Chipp app to authenticate with their own account on third-party services. Instead of using a single shared API key, each user connects their personal account (e.g., their Salesforce, HubSpot, or Google account), and the AI agent acts on their behalf.
## When to Use Consumer OAuth
**Use Consumer OAuth when:**
- Users need to access **their own data** (CRM contacts, calendar events, emails)
- The third-party API requires **user-specific authentication**
- You want **accountability** for which user performed each action
- The service enforces **per-user rate limits or permissions**
**Use Bearer Token when:**
- Accessing **shared company resources** that all users can see
- The MCP server connects to **internal APIs** you control
- You want the **simplest possible setup**
---
## How Consumer OAuth Works
1. **User initiates chat** with your Chipp app
2. **AI agent calls a tool** that requires user authentication
3. **Chipp detects** the user hasn't connected their account
4. **User sees OAuth prompt** and clicks to authenticate
5. **Browser opens** the third-party's login page
6. **User authorizes** your app to access their data
7. **Callback returns** with authorization code
8. **Chipp exchanges** code for access & refresh tokens
9. **Tokens stored** securely per-user in the database
10. **Tool executes** with the user's access token
Subsequent requests use the stored tokens. Chipp automatically refreshes expired tokens using the refresh token.
---
## Configuration
Configure Consumer OAuth in the **Pro Actions** modal when adding a custom MCP server.
### Step 1: Create OAuth App on Third-Party Service
First, register an OAuth application with the service you want to integrate (e.g., Salesforce, HubSpot, Google).
You'll need to configure:
| Setting | Value |
|---------|-------|
| **Redirect URI** | `https://app.chipp.ai/api/chat/mcp/oauth/callback` |
| **Scopes** | Request the minimum scopes needed for your tools |
After registration, you'll receive:
- **Client ID** - Public identifier for your OAuth app
- **Client Secret** - Private key (keep this secure!)
### Step 2: Find OAuth Endpoints
Locate the OAuth 2.0 endpoints in the service's documentation:
| Endpoint | Description | Example |
|----------|-------------|---------|
| **Authorize URL** | Where users grant permission | `https://login.salesforce.com/services/oauth2/authorize` |
| **Token URL** | Where codes are exchanged for tokens | `https://login.salesforce.com/services/oauth2/token` |
### Step 3: Configure in Chipp
1. Go to your app's **Build** tab
2. Open the **Pro Actions** modal
3. Click **Add Custom MCP Server**
4. Enter your **MCP Server URL**
5. Select **Consumer OAuth (per-user)** as the auth type
Fill in the OAuth configuration:
### Step 4: Test the Configuration
Click **Test OAuth Configuration** to verify your setup works:
1. A new window opens with the third-party's login page
2. Log in and authorize the connection
3. The window closes and shows a success message
4. If successful, your MCP server's tools are fetched and cached
Test with your own account first. If the OAuth flow completes and tools load successfully, the configuration is correct.
---
## Token Request Styles
Different OAuth providers expect credentials formatted differently during the token exchange. Choose the style that matches your provider:
### form_post (Most Common)
Credentials sent as form-encoded body parameters. **Try this first.**
```http
POST /oauth/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTH_CODE&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET&
redirect_uri=https://app.chipp.ai/api/chat/mcp/oauth/callback
```
**Used by:** Google, GitHub, LinkedIn, most OAuth providers
### json_basic
Credentials sent in HTTP Basic Authorization header, body as JSON.
```http
POST /oauth/token HTTP/1.1
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/json
{
"grant_type": "authorization_code",
"code": "AUTH_CODE",
"redirect_uri": "https://app.chipp.ai/api/chat/mcp/oauth/callback"
}
```
**Used by:** Some enterprise APIs, Stripe
### form_basic
Credentials in Basic header, body as form-encoded.
```http
POST /oauth/token HTTP/1.1
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTH_CODE&
redirect_uri=https://app.chipp.ai/api/chat/mcp/oauth/callback
```
**Used by:** Salesforce, some enterprise OAuth providers
If token exchange fails, try a different token style. The error message often doesn't indicate the wrong style was used.
---
## Passing User Tokens to Your MCP Server
When a user has authenticated, Chipp passes their access token to your MCP server in the `Authorization` header:
```http
POST /mcp HTTP/1.1
Authorization: Bearer USER_ACCESS_TOKEN
Content-Type: application/json
```
Your MCP server can extract this token and use it for API calls:
```typescript
// In your MCP server
server.tool(
"get_my_contacts",
"Fetch the current user's contacts from Salesforce",
{},
async (args, context) => {
// The user's access token is in the Authorization header
const userToken = context?.meta?.authorization?.replace("Bearer ", "");
const response = await fetch("https://your-instance.salesforce.com/services/data/v58.0/sobjects/Contact", {
headers: {
"Authorization": `Bearer ${userToken}`
}
});
const contacts = await response.json();
return {
content: [{ type: "text", text: JSON.stringify(contacts, null, 2) }]
};
}
);
```
---
## Token Refresh
Access tokens expire. Chipp automatically handles token refresh:
1. Before each MCP call, Chipp checks if the token is expired (with 5-minute buffer)
2. If expired and a refresh token exists, Chipp requests a new access token
3. The new token is stored and used for the request
4. If refresh fails, the user is prompted to re-authenticate
Your MCP server doesn't need to handle token refresh - Chipp manages it automatically.
Some OAuth providers (like Google) only return a refresh token on the **first** authorization. If users need to re-authenticate, they may need to revoke access in their account settings first.
---
## User Experience
When a user chats with your app and the AI needs to access a tool requiring Consumer OAuth:
1. **First time:** User sees a message prompting them to connect their account
2. **Click to connect:** Opens the OAuth provider's login/consent page
3. **Authorize:** User grants your app access to their data
4. **Automatic return:** Browser closes, chat continues seamlessly
5. **Subsequent chats:** No prompts - tokens are reused automatically
### What Users See
```
🔐 This action requires access to your Salesforce account.
[Connect Salesforce Account]
Click above to securely connect. We'll only request access to the
data needed for this app.
```
After connecting:
```
✓ Connected to Salesforce as john@example.com
Now fetching your recent contacts...
```
---
## Security Considerations
---
## Troubleshooting
### "Invalid redirect_uri" Error
The redirect URI in your OAuth app configuration doesn't match what Chipp sends.
**Solution:** Ensure your OAuth app's redirect URI is exactly:
```
https://app.chipp.ai/api/chat/mcp/oauth/callback
```
### "Invalid client" Error
The client ID or secret is incorrect.
**Solution:** Double-check credentials. Some providers have separate credentials for production vs. sandbox environments.
### Token Exchange Fails Silently
The token request style doesn't match what the provider expects.
**Solution:** Try each token request style (form_post, json_basic, form_basic) until one works.
### "Access Denied" During Authorization
User declined the consent prompt, or the requested scopes aren't allowed.
**Solution:**
- Check if the user clicked "Deny" instead of "Allow"
- Verify your OAuth app has access to the requested scopes
- Some scopes require app review/approval
### Refresh Token Missing
The OAuth provider only issues refresh tokens on initial authorization.
**Solution:**
- User should revoke access in the provider's settings
- Re-authorize to get a new refresh token
- Some providers require adding `access_type=offline` or `prompt=consent` to the authorize URL
### Tools Don't Load After OAuth
The OAuth succeeded but tools aren't showing.
**Solution:**
- Check that your MCP server is running and accessible
- Verify the server URL is correct
- Check MCP server logs for errors
- Ensure the server responds to `tools/list` correctly
---
## Example: Salesforce Integration
Here's a complete example configuring Consumer OAuth for Salesforce:
### 1. Create Connected App in Salesforce
1. Go to **Setup** → **Apps** → **App Manager**
2. Click **New Connected App**
3. Configure:
- **Connected App Name:** My Chipp Integration
- **API Name:** My_Chipp_Integration
- **Enable OAuth Settings:** ✓
- **Callback URL:** `https://app.chipp.ai/api/chat/mcp/oauth/callback`
- **Selected OAuth Scopes:** `api`, `refresh_token`, `offline_access`
4. Save and wait for activation (can take 10 minutes)
5. Copy **Consumer Key** (Client ID) and **Consumer Secret**
### 2. Configure in Chipp
| Field | Value |
|-------|-------|
| OAuth Client ID | `3MVG9...` (your Consumer Key) |
| OAuth Client Secret | `ABC123...` (your Consumer Secret) |
| Authorize URL | `https://login.salesforce.com/services/oauth2/authorize` |
| Token URL | `https://login.salesforce.com/services/oauth2/token` |
| Scopes | `api refresh_token offline_access` |
| Token Request Style | `form_basic` |
### 3. Build Your MCP Server
```typescript
server.tool(
"search_salesforce_contacts",
"Search for contacts in the user's Salesforce org",
{
query: z.string().describe("Search query for contact name or email")
},
async ({ query }, context) => {
const token = context?.meta?.authorization?.replace("Bearer ", "");
// Get instance URL from token info (simplified)
const instanceUrl = "https://your-instance.salesforce.com";
const soql = `SELECT Id, Name, Email, Phone FROM Contact WHERE Name LIKE '%${query}%' LIMIT 10`;
const response = await fetch(
`${instanceUrl}/services/data/v58.0/query?q=${encodeURIComponent(soql)}`,
{
headers: { "Authorization": `Bearer ${token}` }
}
);
const results = await response.json();
return {
content: [{ type: "text", text: JSON.stringify(results.records, null, 2) }]
};
}
);
```
---
## Related Guides
- [Building Custom MCP Servers](/docs/guides/building-custom-mcp-servers) - Complete guide to building MCP servers
- [Pro Actions Getting Started](/docs/pro-actions/getting-started) - Use pre-built MCP integrations
- [Chipp MCP Server](/docs/guides/chipp-mcp-server) - Connect Claude Code to manage your Chipp apps