@startuml hermes-mcp-userflows skinparam backgroundColor #1a1a2e skinparam defaultFontColor #e0e0e0 skinparam defaultFontSize 12 skinparam sequenceArrowColor #aaaaaa skinparam sequenceLifeLineBorderColor #34495e skinparam sequenceParticipantBorderColor #0f3460 skinparam sequenceParticipantBackgroundColor #16213e skinparam sequenceParticipantFontColor #e0e0e0 skinparam noteBackgroundColor #0d1b2a skinparam noteBorderColor #533483 skinparam noteFontColor #e0e0e0 skinparam sequenceGroupBorderColor #e94560 skinparam sequenceGroupFontColor #e94560 skinparam sequenceGroupBodyBackgroundColor #0d0d1a title hermes-mcp - User Flows (post multi-tenancy, 2026-05-08) ' ════════════════════════════════════════════════════════════════ == Flow 1: Single-User Tool Call (builder prototype - unchanged) == ' ════════════════════════════════════════════════════════════════ participant "Claude.ai" as claude #16213e participant "MCP Transport\n/mcp" as mcp #16213e participant "requireAuth" as auth #16213e participant "handleToolCall\n(no customer)" as handler #16213e participant "Platform Client\nsrc/clients/*" as client #16213e participant "Platform API" as api #0f3460 claude -> mcp : POST /mcp Bearer token mcp -> auth : validate token auth -> auth : match MCP_API_KEY env var auth --> mcp : OK mcp -> handler : handleToolCall(name, args)\ncustomer = undefined handler -> client : sendMessage(args, undefined) note right of client customer is undefined -> falls back to env vars WHATSAPP_DEFAULT_ACCESS_TOKEN etc. end note client -> api : POST with env-var token api --> client : response client --> claude : tool result ' ════════════════════════════════════════════════════════════════ == Flow 2: Customer Onboarding - Connect WhatsApp == ' ════════════════════════════════════════════════════════════════ participant "Customer\n(squaremcp.com)" as cust #16213e participant "POST /api/connect/whatsapp" as conn_ep #16213e participant "meterMiddleware" as meter #16213e participant "MySQL\ncustomers table" as mysql #0f3460 participant "Redis" as redis #0f3460 participant "credential-store\n(AES-256-GCM)" as cs #16213e participant "webhook-router" as wr #16213e cust -> conn_ep : POST /api/connect/whatsapp\nx-api-key: cust_abc123\n{phoneNumberId, accessToken, businessAccountId} conn_ep -> meter : resolve customer meter -> redis : GET customer:apikey:cust_abc123 redis --> meter : (miss - first request) meter -> mysql : SELECT id,plan,active,email\nFROM customers WHERE api_key=? mysql --> meter : {id:'cust_001', plan:'starter', active:true} meter -> redis : SETEX customer:apikey:cust_abc123 60 {...} meter --> conn_ep : req.customer = Customer{id:'cust_001', getCredential()} conn_ep -> cs : storeCredential('cust_001', 'whatsapp', creds) cs -> cs : randomBytes(12) IV\nAES-256-GCM encrypt cs -> redis : SET creds:cust_001:whatsapp [encrypted] conn_ep -> wr : registerWhatsAppNumber('cust_001', phoneNumberId) wr -> redis : SET wa_phone_id:{phoneNumberId} cust_001 conn_ep --> cust : 200 {connected: true, platform: 'whatsapp'} ' ════════════════════════════════════════════════════════════════ == Flow 3: Multi-Tenant Tool Call (customer sends WhatsApp via MCP/REST) == ' ════════════════════════════════════════════════════════════════ participant "Customer\nMCP session" as cust2 #16213e participant "handleToolCall\n(customer passed)" as handler2 #16213e participant "whatsapp.ts\nclient" as wac #16213e participant "credential-store" as cs2 #16213e participant "Redis" as redis2 #0f3460 participant "audit-log" as al #16213e participant "WhatsApp\nGraph API" as wa_api #0f3460 cust2 -> handler2 : whatsapp_send_message\n{to, message}\ncustomer = Customer{id:'cust_001'} handler2 -> wac : sendMessage(args, customer) wac -> cs2 : customer.getCredential('whatsapp') cs2 -> redis2 : GET creds:cust_001:whatsapp redis2 --> cs2 : [AES-256-GCM ciphertext] cs2 -> cs2 : decrypt -> {phoneNumberId, accessToken, businessAccountId} cs2 --> wac : WhatsAppCredentials wac -> wa_api : POST /{phoneNumberId}/messages\nAuthorization: Bearer {accessToken} wa_api --> wac : {messages: [{id: 'wamid.xxx'}]} wac -> al : audit.success({to}) al -> redis2 : SET logs:cust_001:2026-05-08:00000001\nEX 7776000 (90 days) wac --> cust2 : {success: true, message_id: 'wamid.xxx'} ' ════════════════════════════════════════════════════════════════ == Flow 4: Inbound WhatsApp Webhook (Meta -> correct customer) == ' ════════════════════════════════════════════════════════════════ participant "Meta\nCloud API" as meta #0f3460 participant "POST /webhook/whatsapp" as whook #16213e participant "webhook-router" as wr2 #16213e participant "Redis" as redis3 #0f3460 participant "credential-store" as cs3 #16213e participant "handleInbound\n(stub -> future agent)" as inbound #16213e meta -> whook : POST /webhook/whatsapp\n{object:'whatsapp_business_account',\nentry:[{changes:[{value:{metadata:\n{phone_number_id:'111'},messages:[...]}}]}]} note right of whook Must respond within 20s or Meta retries the webhook end note whook --> meta : 200 EVENT_RECEIVED <- immediate whook -> wr2 : routeWhatsAppWebhook(body) [async] wr2 -> redis3 : GET wa_phone_id:111 redis3 --> wr2 : 'cust_001' wr2 -> cs3 : getCredential('cust_001', 'whatsapp') cs3 -> redis3 : GET creds:cust_001:whatsapp -> decrypt cs3 --> wr2 : WhatsAppCredentials wr2 --> whook : RoutedWebhookEvent{customerId, phoneNumberId, message, credentials} whook -> inbound : handleInboundWhatsAppMessage(event) note right of inbound Currently: log only Future: route to customer's AI agent / queue end note ' ════════════════════════════════════════════════════════════════ == Flow 5: Customer Checks Connection Status == ' ════════════════════════════════════════════════════════════════ participant "Customer" as cust3 #16213e participant "GET /api/connections" as conn_get #16213e participant "meterMiddleware" as meter3 #16213e participant "credential-store" as cs4 #16213e participant "Redis" as redis4 #0f3460 cust3 -> conn_get : GET /api/connections\nx-api-key: cust_abc123 conn_get -> meter3 : resolve (cache hit this time) meter3 -> redis4 : GET customer:apikey:cust_abc123 redis4 --> meter3 : Customer{id:'cust_001'} meter3 --> conn_get : req.customer attached loop for each platform in [email, whatsapp, linkedin, telegram, discord, instagram, twitter, obsidian] conn_get -> cs4 : customer.getCredential(platform) cs4 -> redis4 : GET creds:cust_001:{platform} redis4 --> cs4 : value or null end conn_get --> cust3 : 200 {customerId:'cust_001',\nconnections:{whatsapp:true, linkedin:false, ...}} @enduml