Added
- MCP server (opt-in,
MCP_ENABLED=true): exposes a curated ~39-tool agent surface (sessions, messaging, contacts, basic group ops, webhook reads) over the Model Context Protocol atPOST /mcp, on the existing single port. Off by default — the MCP SDK is not loaded unless enabled, and every REST route is unchanged. Tools call the existing services and reuse the same API-key auth, role, and per-session scoping as REST; reads vs writes are tiered andMCP_READONLY=truemounts read tools only. Destructive/privileged operations are deliberately excluded from the surface. (relates to #256; salvages result-shaping from #461 — thanks @tobiasstrebitzer) - Client SDKs: official, hand-written client libraries for the REST API in JavaScript/TypeScript (
@rmyndharis/openwa), Python (rmyndharis-openwa), and PHP (rmyndharis/openwa), replacing the previous single-file stub (sdk/). Each exposes the same fluent resource surface —sessions,messages,contacts,groups,chats,webhooks,labels,channels,catalog,status,templates,health— over an injectable HTTP transport, with a typed error hierarchy mapping the NestJS error envelope (401/403/404/409/429/501) to typed exceptions and a timeout error. Request/response types mirror the server DTOs exactly. The JavaScript package ships dual CJS + ESM with bundled type declarations (consumable via bothrequire()and nativeimport(), guarded by a packaging smoke test); the Python package ships PEP 561 type information (py.typed); the PHP package is PSR-4 / Guzzle 7. Redirects are never auto-followed (so the API key is never re-sent to a redirect target), auth/JSON headers always take precedence over caller-supplied defaults, path segments are percent-encoded, and a base-URL path prefix (e.g. behind a reverse proxy) is preserved. The SDK does not retry — wrap calls with your own backoff. Published at0.1.0on npm (@rmyndharis/openwa), PyPI (rmyndharis-openwa), and Packagist (rmyndharis/openwa). (#463)
Changed
- CI now runs the JavaScript, Python, and PHP client SDK test suites (path-filtered to
sdk/**, including the dual-format CJS/ESM packaging smoke test), and the Packagist mirror of the PHP SDK is gated on its tests passing so a broken SDK can no longer auto-publish.
Fixed
- Reconnection no longer stalls when a wedged browser fails to shut down: the engine teardown during an automatic reconnect is now time-bounded (10s, matching every other teardown), so a stuck Chromium can't leave a session permanently disconnected without self-healing. Affects the
whatsapp-web.jsengine; recover an already-stuck session with force-kill. - Message timestamps are now consistently returned as a number on both SQLite and PostgreSQL. PostgreSQL previously returned the
bigintcolumn as a string, which broke strictly-typed SDK clients and arithmetic in non-coercing consumers. - A blank
DATABASE_PASSWORDforwarded by the bundled Docker Compose file is now treated as unset, so an external-PostgreSQL password saved via the dashboard is applied instead of being shadowed by the empty value (a real host/.envvalue still keeps top precedence). - The Python and PHP SDKs now treat an unfollowed redirect (any
3xx) as an error response, matching the JavaScript SDK. Redirects are never followed (so the API key is never re-sent to the target), which makes a3xxan unusable result rather than a fake success. - Duplicate inbound webhook deliveries: a single inbound WhatsApp message could reach a registered webhook (and the
messagestable) more than once because the engine can re-fire the message event. Inboundmessage.receivedis now de-duplicated server-side, enforced by aUNIQUE(sessionId, waMessageId)constraint (added with a lossless de-duplicating migration), so each message is persisted and dispatched once. The guard fails open — a transient DB error still delivers the message. Webhook delivery remains at-least-once (engines re-fire, failed deliveries retry), so handlers should still be idempotent on theX-OpenWA-Idempotency-Keyheader, now documented under Webhook Delivery Semantics. (#464)