@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<WhatsAppCredentials>('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
