Snappy MCP Server Integration Guide
Overview
This document provides a comprehensive guide for integrating external AI systems (su ch as Salesforce AgentForce or Telegram bots) with our MCP (Model Context Protocol) server for automated gift campaign management.
Prerequisites
Before integrating with our MCP server, ensure the following are already set up in your Snappy dashboard:
🏢 Account Setup Required
- User Account: Valid user credentials (username/password + optional TOTP)
- Company: At least one company associated with your account
- Account: At least one account under your company
- Collections: One or more product collections configured for your account
- Recipients: Contact lists or individual recipients (can be managed via API)
AgentForce Integration
🤖 Salesforce AgentForce Setup
1. MCP Server Configuration
// AgentForce MCP Configuration
const mcpConfig = {
serverUrl: "<https://your-mcp-server.com/mcp>",
transport: "http", // or "sse" for real-time updates
authentication: {
type: "user_credentials",
endpoint: "/auth/sign-in"
}
};
2. AgentForce Action Configuration
// Salesforce Apex class for MCP integration
public class SnappyGiftMCPIntegration {
@InvocableMethod(label='Send Snappy Gift Campaign')
public static List<GiftCampaignResult> sendGiftCampaign(List<GiftCampaignRequest> requests) {
List<GiftCampaignResult> results = new List<GiftCampaignResult>();
for (GiftCampaignRequest request : requests) {
try {
// 1. Authenticate with MCP server
String sessionToken = authenticateUser(request.username, request.password);
// 2. Execute gift campaign workflow
GiftCampaignResult result = executeGiftWorkflow(sessionToken, request);
results.add(result);
} catch (Exception e) {
results.add(new GiftCampaignResult(false, e.getMessage()));
}
}
return results;
}
private static String authenticateUser(String username, String password) {
// Call MCP server sign-in tool
HttpRequest req = new HttpRequest();
req.setEndpoint('<https://your-mcp-server.com/mcp>');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
Map<String, Object> mcpRequest = new Map<String, Object>{
'method' => 'tools/call',
'params' => new Map<String, Object>{
'name' => 'sign-in',
'arguments' => new Map<String, Object>{
'username' => username,
'password' => password
}
}
};
req.setBody(JSON.serialize(mcpRequest));
HttpResponse res = new Http().send(req);
// Parse response and extract session token
return parseAuthResponse(res.getBody());
}
}
3. AgentForce Topic Configuration
# AgentForce Topic: Gift Campaign Management
topic_name: "Gift Campaign Management"
description: "Automate gift campaigns through Snappy integration"
# Available Actions
actions:
- name: "Create Gift Campaign"
description: "Create and send a personalized gift campaign"
parameters:
- recipient_email: "Email of the gift recipient"
- occasion: "Occasion for the gift (birthday, work anniversary, etc.)"
- budget_range: "Budget range for gifts ($25-$50, $50-$100, etc.)"
- collection_preference: "Preferred collection type (optional)"
- custom_message: "Personal message for the recipient"
# Integration with MCP Tools
mcp_tools:
- "sign-in"
- "get-account-collections"
- "get-collection-products"
- "create-campaign"
- "send-gifts"
- "create-email"
- "create-greeting"
- "choose-reveal"
Authentication & Authorization
🔐 Authentication Flow
sequenceDiagram participant AF as AgentForce participant MCP as MCP Server participant Auth as AWS Cognito participant Snappy as Snappy APIs AF->>MCP: POST /mcp (sign-in tool) MCP->>Auth: Authenticate user Auth-->>MCP: JWT tokens MCP-->>AF: Success + session established AF->>MCP: Subsequent tool calls MCP->>Snappy: API calls with token Snappy-->>MCP: Data response MCP-->>AF: Tool response
Step 1: User Authentication
{
"method": "tools/call",
"params": {
"name": "sign-in",
"arguments": {
"username": "[email protected]",
"password": "secure_password",
"totp": "123456"
}
}
}
Response:
{
"content": [
{
"type": "text",
"text": "Successfully signed in. Available accounts: [Account data]"
}
]
}
Step 2: Session Management
For HTTP transport: Each request must include authentication
For SSE transport: Session is maintained automatically after sign-in
Available Tools & Endpoints
📋 Core Tools Reference
Tool Name | Purpose | Required Auth | Key Parameters |
---|---|---|---|
sign-in | User authentication | No | username , password , totp? |
check-sign-in | Verify auth status | No | None |
get-account-collections | List available collections | Yes | accountId |
get-collection-products | Get products in collection | Yes | collectionId , maxBudget? , country? |
create-campaign | Create gift campaign | Yes | companyId , accountId , collectionId , name , budgets , recipientsIds |
send-gifts | Send campaign gifts | Yes | campaignId , recipients , budget , productCollectionId |
create-email | Generate email content | Yes | occasion , tone? , primaryColor? |
create-greeting | Generate greeting card | Yes | occasion , tone? , primaryColor? |
choose-reveal | Select gift reveal experience | Yes | occasion , brandColors? |
🔍 Tool Details
get-account-collections
{
"method": "tools/call",
"params": {
"name": "get-account-collections",
"arguments": {
"accountId": "account_12345"
}
}
}
Response:
{
"content": [{
"type": "text",
"text": "[{\\"_id\\":\\"coll_123\\",\\"name\\":\\"Premium Business Collection\\",\\"rank\\":1},{\\"_id\\":\\"coll_456\\",\\"name\\":\\"Holiday Special Collection\\",\\"rank\\":2}]"
}]
}
create-campaign
{
"method": "tools/call",
"params": {
"name": "create-campaign",
"arguments": {
"companyId": "comp_789",
"accountId": "acc_123",
"collectionId": "coll_456",
"name": "Q4 Employee Appreciation Campaign",
"maxBudget": 75,
"minBudget": 25,
"recipientsIds": ["recipient_1", "recipient_2"],
"email": {
"mailSubject": "A special gift for you!",
"mailGreeting": "Dear {receiver_first_name}",
"mailBody": "We appreciate your hard work this quarter.",
"mailSignature": "Best regards, The Team"
}
}
}
}
Complete Workflow Examples
🎯 Example 1: AgentForce Birthday Campaign
// Salesforce Flow: Birthday Gift Campaign
public class BirthdayGiftFlow {
@InvocableMethod
public static void sendBirthdayGift(List<EmployeeRecord> employees) {
for (EmployeeRecord emp : employees) {
// Step 1: Authenticate
MCPSession session = MCPClient.authenticate(
System.Label.SnappyUsername,
System.Label.SnappyPassword
);
// Step 2: Get collections
List<Collection> collections = session.getAccountCollections(emp.AccountId);
Collection birthdayCollection = findBirthdayCollection(collections);
// Step 3: Create personalized content
EmailContent email = session.createEmail(new Map<String, Object>{
'occasion' => 'birthday',
'tone' => 'warm and celebratory'
});
GreetingContent greeting = session.createGreeting(new Map<String, Object>{
'occasion' => 'birthday',
'tone' => 'personal'
});
// Step 4: Create campaign
Campaign campaign = session.createCampaign(new Map<String, Object>{
'companyId' => emp.CompanyId,
'accountId' => emp.AccountId,
'collectionId' => birthdayCollection.Id,
'name' => emp.FirstName + ' Birthday Gift ' + Date.today().year(),
'maxBudget' => 50,
'minBudget' => 25,
'recipientsIds' => new List<String>{emp.RecipientId},
'email' => email.toMap()
});
// Step 5: Send gifts
session.sendGifts(new Map<String, Object>{
'campaignId' => campaign.Id,
'recipients' => new Map<String, Object>{
'type' => 'ids',
'recipientsList' => new List<String>{emp.RecipientId}
},
'budget' => new Map<String, Object>{
'plan' => 40,
'max' => 50,
'min' => 25
},
'productCollectionId' => birthdayCollection.Id
});
}
}
}
🎯 Example 2: Telegram Bot Integration
// Telegram Bot: Gift Campaign Handler
const TelegramBot = require('node-telegram-bot-api');
const MCPClient = require('./mcp-client');
class SnappyGiftBot {
constructor(token) {
this.bot = new TelegramBot(token, { polling: true });
this.mcpClient = new MCPClient('<https://your-mcp-server.com>');
this.setupHandlers();
}
setupHandlers() {
// Handle /sendgift command
this.bot.onText(/\\/sendgift (.+)/, async (msg, match) => {
const chatId = msg.chat.id;
const params = this.parseGiftParams(match[1]);
try {
// Authenticate user
await this.mcpClient.signIn(
process.env.SNAPPY_USERNAME,
process.env.SNAPPY_PASSWORD
);
// Execute gift workflow
const result = await this.executeGiftWorkflow(params);
this.bot.sendMessage(chatId,
`✅ Gift sent successfully!\\n` +
`Campaign: ${result.campaignName}\\n` +
`Recipient: ${params.recipientEmail}\\n` +
`Collection: ${result.collectionName}`
);
} catch (error) {
this.bot.sendMessage(chatId, `❌ Error: ${error.message}`);
}
});
}
async executeGiftWorkflow(params) {
// Get collections
const collections = await this.mcpClient.getAccountCollections(params.accountId);
const selectedCollection = this.selectBestCollection(collections, params.occasion);
// Generate personalized content
const [email, greeting, reveal] = await Promise.all([
this.mcpClient.createEmail({
occasion: params.occasion,
tone: 'professional'
}),
this.mcpClient.createGreeting({
occasion: params.occasion
}),
this.mcpClient.chooseReveal({
occasion: params.occasion
})
]);
// Create and send campaign
const campaign = await this.mcpClient.createCampaign({
companyId: params.companyId,
accountId: params.accountId,
collectionId: selectedCollection._id,
name: `${params.occasion} Gift - ${new Date().toLocaleDateString()}`,
maxBudget: params.maxBudget,
minBudget: params.minBudget,
recipientsIds: [params.recipientId],
email: JSON.parse(email.content)
});
await this.mcpClient.sendGifts({
campaignId: campaign.created._id,
recipients: {
type: 'ids',
recipientsList: [params.recipientId]
},
budget: {
plan: params.budget,
max: params.maxBudget,
min: params.minBudget
},
productCollectionId: selectedCollection._id
});
return {
campaignName: campaign.name,
collectionName: selectedCollection.name
};
}
}
// Usage
const bot = new SnappyGiftBot(process.env.TELEGRAM_BOT_TOKEN);
Error Handling
⚠️ Common Error Scenarios
Authentication Errors
{
"error": {
"code": "AUTH_FAILED",
"message": "Login failed",
"details": "Invalid username or password"
}
}
Handling:
try {
await mcpClient.signIn(username, password);
} catch (error) {
if (error.code === 'AUTH_FAILED') {
// Prompt for correct credentials
// Implement retry logic with backoff
}
}
TOTP Required
{
"content": [{
"type": "text",
"text": "Please enter your TOTP code from your authenticator app"
}]
}
Handling:
if (response.content[0].text.includes('TOTP code')) {
const totp = await promptUserForTOTP();
await mcpClient.signIn(username, password, totp);
}
Collection/Product Not Found
{
"error": {
"code": "NOT_FOUND",
"message": "Collection not found",
"collectionId": "invalid_collection_id"
}
}
Budget/Country Restrictions
{
"error": {
"code": "NO_PRODUCTS_AVAILABLE",
"message": "No products available for the specified criteria",
"details": {
"budget": 25,
"country": "US",
"collectionId": "coll_123"
}
}
}
🔄 Retry Strategies
class MCPErrorHandler {
static async withRetry(operation, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
if (attempt === maxRetries || !this.isRetryableError(error)) {
throw error;
}
const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
await this.sleep(delay);
}
}
}
static isRetryableError(error) {
const retryableCodes = ['NETWORK_ERROR', 'TIMEOUT', 'SERVER_ERROR'];
return retryableCodes.includes(error.code);
}
static sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
Best Practices
✅ Security Best Practices
-
Credential Management
// ❌ Don't hardcode credentials const username = '[email protected]'; // ✅ Use environment variables or secure storage const username = process.env.SNAPPY_USERNAME; const password = await getSecureCredential('SNAPPY_PASSWORD');
-
Session Management
// ✅ Implement session reuse class MCPSessionManager { constructor() { this.session = null; this.lastAuth = null; } async getValidSession() { if (this.isSessionExpired()) { this.session = await this.authenticate(); this.lastAuth = Date.now(); } return this.session; } }
🚀 Performance Optimization
-
Parallel Execution
// ✅ Execute independent operations in parallel const [email, greeting, reveal] = await Promise.all([ mcpClient.createEmail(params), mcpClient.createGreeting(params), mcpClient.chooseReveal(params) ]);
-
Caching Strategy
// ✅ Cache collections data class CollectionCache { constructor(ttl = 300000) { // 5 minutes this.cache = new Map(); this.ttl = ttl; } async getCollections(accountId) { const key = `collections_${accountId}`; const cached = this.cache.get(key); if (cached && (Date.now() - cached.timestamp) < this.ttl) { return cached.data; } const collections = await mcpClient.getAccountCollections(accountId); this.cache.set(key, { data: collections, timestamp: Date.now() }); return collections; } }
📝 Logging & Monitoring
class MCPLogger {
static logToolCall(toolName, params, duration, success) {
console.log({
timestamp: new Date().toISOString(),
tool: toolName,
parameters: this.sanitizeParams(params),
duration_ms: duration,
success: success,
session_id: this.getSessionId()
});
}
static sanitizeParams(params) {
// Remove sensitive data
const sanitized = { ...params };
delete sanitized.password;
delete sanitized.totp;
return sanitized;
}
}
Troubleshooting
🔍 Debugging Steps
-
Check Authentication Status
const authStatus = await mcpClient.checkSignIn(); console.log('Auth Status:', authStatus);
-
Validate Account Setup
// Verify account has collections const collections = await mcpClient.getAccountCollections(accountId); if (collections.length === 0) { throw new Error('No collections available. Please set up collections in dashboard.'); }
-
Test Individual Tools
// Test each tool independently await mcpClient.testTool('get-account-collections', { accountId }); await mcpClient.testTool('get-collection-products', { collectionId, maxBudget: 100 });
📞 Support & Documentation
- API Documentation: [Internal API Docs]
- Dashboard Setup: [Dashboard Guide]
- MCP Protocol: [MCP Specification]
- Support Contact: [[email protected]]
This integration guide enables external AI systems to seamlessly interact with Snappy's gift campaign management through our MCP server, providing a standardized and secure way to automate personalized gifting workflows.