feat: multi-tenant credential isolation + architecture docs
- Add src/multitenancy/ with AES-256-GCM credential store, WhatsApp webhook router (phone_number_id -> customerId), and per-customer audit log (90-day Redis TTL) - Add src/billing/ with plan definitions and meterMiddleware that resolves API key -> Customer object with getCredential() closure - Refactor all src/clients/* to accept optional customer param, falling back to env vars for backward compat with single-user mode - Thread customer through handleToolCall(name, args, customer?) - Add customers table to MySQL schema initDatabase() - Add /webhook/whatsapp (immediate 200 + async routing) and /api/connect/* onboarding endpoints to index.ts - Add Redis 7 to docker-compose.yml; add REDIS_URL and CREDENTIAL_ENCRYPTION_KEY to hermes-k8s.yaml - Add product/incubation/ with architecture write-up and PlantUML diagrams (system architecture + 5 user flows) - Extend OpenAPI spec in manifest.ts with all platform endpoints Verification: 3 isolation tests (credential, webhook routing, audit log) passed against live Redis. Deployed to hermes.squaremcp.com. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
159
product/incubation/architecture-system.puml
Normal file
159
product/incubation/architecture-system.puml
Normal file
@@ -0,0 +1,159 @@
|
||||
@startuml hermes-mcp-architecture
|
||||
|
||||
skinparam backgroundColor #1a1a2e
|
||||
skinparam defaultFontColor #e0e0e0
|
||||
skinparam defaultFontSize 13
|
||||
skinparam arrowColor #888888
|
||||
skinparam roundCorner 8
|
||||
skinparam shadowing false
|
||||
|
||||
skinparam component {
|
||||
BackgroundColor #16213e
|
||||
BorderColor #0f3460
|
||||
FontColor #e0e0e0
|
||||
ArrowColor #888888
|
||||
}
|
||||
skinparam package {
|
||||
BackgroundColor #0d1b2a
|
||||
BorderColor #2a4a6a
|
||||
FontColor #aaaaaa
|
||||
FontStyle bold
|
||||
}
|
||||
skinparam database {
|
||||
BackgroundColor #0f2040
|
||||
BorderColor #533483
|
||||
FontColor #e0e0e0
|
||||
}
|
||||
skinparam cloud {
|
||||
BackgroundColor #1a0d2e
|
||||
BorderColor #e94560
|
||||
FontColor #e0e0e0
|
||||
}
|
||||
skinparam note {
|
||||
BackgroundColor #0d1b2a
|
||||
BorderColor #2a4a6a
|
||||
FontColor #aaaaaa
|
||||
}
|
||||
|
||||
title hermes-mcp -- System Architecture (2026-05-08)
|
||||
|
||||
' ── AI Clients ────────────────────────────────────────────────────
|
||||
package "AI Clients" {
|
||||
[Claude.ai\nMCP connector] as claude
|
||||
[ChatGPT\ncustom GPT] as chatgpt
|
||||
[opencode / Codex] as opencode
|
||||
}
|
||||
|
||||
' ── hermes-mcp server ─────────────────────────────────────────────
|
||||
package "hermes-mcp | Node.js / TypeScript | hermes.squaremcp.com" {
|
||||
|
||||
package "Transports" {
|
||||
[Streamable HTTP\n/mcp] as mcp_t
|
||||
[SSE legacy\n/sse] as sse_t
|
||||
[REST API\n/api/*] as rest_t
|
||||
[Webhook\n/webhook/whatsapp] as wh_t
|
||||
}
|
||||
|
||||
package "Auth" {
|
||||
[requireAuth\nMCP_API_KEY + OAuth Bearer] as req_auth
|
||||
[meterMiddleware\nAPI key -> Customer] as meter
|
||||
[OAuth 2.0\n/oauth/*] as oauth
|
||||
}
|
||||
|
||||
package "Core" {
|
||||
[handleToolCall\nname, args, customer?] as dispatch
|
||||
}
|
||||
|
||||
package "Platform Clients src/clients/*" {
|
||||
[whatsapp.ts] as c_wa
|
||||
[linkedin.ts] as c_li
|
||||
[telegram.ts] as c_tg
|
||||
[discord.ts] as c_dc
|
||||
[instagram.ts] as c_ig
|
||||
[twitter.ts] as c_tw
|
||||
[obsidian.ts] as c_ob
|
||||
}
|
||||
|
||||
package "Multi-tenancy src/multitenancy/*" {
|
||||
[credential-store\nAES-256-GCM] as cred_store
|
||||
[webhook-router\nphone_id -> customerId] as wh_router
|
||||
[audit-log\n90-day per-customer trail] as audit_log
|
||||
}
|
||||
|
||||
package "Billing src/billing/*" {
|
||||
[middleware.ts\nCustomer + meterMiddleware] as billing
|
||||
[plans.ts\nfree/starter/growth/enterprise] as plans
|
||||
}
|
||||
}
|
||||
|
||||
' ── Storage ───────────────────────────────────────────────────────
|
||||
database "Redis 7" as redis
|
||||
note right of redis
|
||||
creds:{cid}:{platform} (AES-256-GCM encrypted)
|
||||
wa_phone_id:{phoneId} (-> customerId)
|
||||
customer:apikey:{key} (60s TTL cache)
|
||||
logs:{cid}:{date}:{seq} (90-day audit trail)
|
||||
end note
|
||||
|
||||
database "MySQL 8\nhermes_oauth" as mysql
|
||||
note right of mysql
|
||||
customers (id, api_key, plan, active, email)
|
||||
oauth_clients / oauth_auth_codes / oauth_tokens
|
||||
end note
|
||||
|
||||
' ── Platform APIs ─────────────────────────────────────────────────
|
||||
cloud "External Platform APIs" {
|
||||
[Meta Graph API\nWhatsApp + Instagram] as meta_api
|
||||
[LinkedIn API v2] as li_api
|
||||
[Telegram Bot API] as tg_api
|
||||
[Discord API v10] as dc_api
|
||||
[Twitter API v2] as tw_api
|
||||
[Obsidian Vault\nfilesystem] as ob_vault
|
||||
}
|
||||
|
||||
' ── Connections ───────────────────────────────────────────────────
|
||||
claude --> mcp_t : MCP / Bearer
|
||||
chatgpt --> mcp_t : MCP / Bearer
|
||||
opencode --> rest_t : REST / x-api-key
|
||||
|
||||
mcp_t --> req_auth
|
||||
sse_t --> req_auth
|
||||
rest_t --> req_auth
|
||||
rest_t --> meter : /api/connect/*
|
||||
wh_t --> wh_router
|
||||
|
||||
req_auth --> dispatch
|
||||
meter --> billing : resolve Customer
|
||||
billing --> mysql : SELECT customers
|
||||
billing --> redis : customer cache
|
||||
|
||||
dispatch --> c_wa
|
||||
dispatch --> c_li
|
||||
dispatch --> c_tg
|
||||
dispatch --> c_dc
|
||||
dispatch --> c_ig
|
||||
dispatch --> c_tw
|
||||
dispatch --> c_ob
|
||||
|
||||
c_wa --> cred_store : getCredential
|
||||
c_li --> cred_store : getCredential
|
||||
c_tg --> cred_store : getCredential
|
||||
c_dc --> cred_store : getCredential
|
||||
c_ig --> cred_store : getCredential
|
||||
c_tw --> cred_store : getCredential
|
||||
|
||||
cred_store --> redis
|
||||
wh_router --> redis
|
||||
audit_log --> redis
|
||||
|
||||
c_wa --> meta_api
|
||||
c_ig --> meta_api
|
||||
c_li --> li_api
|
||||
c_tg --> tg_api
|
||||
c_dc --> dc_api
|
||||
c_tw --> tw_api
|
||||
c_ob --> ob_vault
|
||||
|
||||
oauth --> mysql
|
||||
|
||||
@enduml
|
||||
Reference in New Issue
Block a user