Files
multica/server/pkg/composio/README.md
Multica Eve 8d0ea04fb0 feat(composio): add standalone Go SDK client (MVP) (#4603)
* feat(composio): add standalone Go SDK client (MVP)

Adds server/pkg/composio — a self-contained Go SDK for the Composio v3.1
REST API. Built on go-resty/resty v2; zero coupling to other Multica
packages so it can be vendored or extracted later without surgery.

MVP surface (just the endpoints Stage 2 needs):

- POST /connected_accounts/link        Client.CreateLink
- POST /tool_router/session            Client.CreateSession
- GET  /connected_accounts             Client.ListConnectedAccounts
- POST /connected_accounts/{id}/revoke Client.RevokeConnection
- DELETE /connected_accounts/{id}      Client.DeleteConnectedAccount
                                       (404 -> nil, idempotent)
- GET  /toolkits                       Client.ListToolkits
- GET  /toolkits/{slug}                Client.GetToolkit
- POST /tools/execute/{slug}           Client.ExecuteTool
- Webhook HMAC-SHA256 verification     composio.VerifyWebhook /
                                       VerifyHTTPRequest + ParseEvent

Other notes:

- Auth via x-api-key header (Composio v3.1 contract).
- Typed *APIError envelope with IsNotFound / IsUnauthorized /
  IsRateLimited helpers; falls back to raw body when upstream returns
  non-JSON.
- Webhook signature accepts the official "v1,<sig>" format and any
  comma-separated multi-version list; 300s replay tolerance by default,
  honors an injectable clock for tests; RFC3339 timestamps tolerated.
- README.md documents all public APIs and design choices.

Tests:

- All exercise httptest.NewServer - no real Composio calls.
- 36 tests covering happy paths, validation, 404 idempotence, error
  decoding, signature verify (good / tampered / stale / multi-version /
  bare / RFC3339 / missing headers / empty secret).
- go test ./pkg/composio/... -cover -> 82.2%, exceeds the >=80% bar.

Follow-ups (separate PRs):

- server/internal/integrations/composio - DB schema, REST handlers,
  registration_service (CSRF), dispatch hook (MUL-3720 remainder).
- Pagination iterators, retry middleware, proxy execute, triggers.

Refs: MUL-3720, MUL-3715
Co-authored-by: multica-agent <github@multica.ai>

* fix(composio): align SDK with v3.1 wire contract (PR #4603 review)

Addresses GPT-Boy's review on PR #4603:

Must-fix
- ListConnectedAccountsRequest: switch UserID/AuthConfigID singular fields
  to plural slices (UserIDs, AuthConfigIDs, ToolkitSlugs, ConnectedAccountIDs,
  Statuses). The Composio v3.1 spec ships these as `*_ids` array params;
  our singular form silently dropped the user-filter on the wire. Also
  surfaces order_by / order_direction / account_type from the same spec.
- ExecuteToolRequest: rename ToolkitVersions -> Version with json tag
  `version` (the actual v3.1 body field). Marks AllowTracing as
  deprecated per the spec.

Nits
- ListToolkitsRequest.SortBy comment: `popular | alphabetical` -> the
  real enum `usage | alphabetically`.
- Client constructor: when Options.HTTPClient is provided, use
  resty.NewWithClient(hc) so the caller's Transport, Jar, CheckRedirect,
  and Timeout all carry through — the prior code only forwarded
  Transport + Timeout despite the field comment promising the full
  *http.Client.

Tests
- TestListConnectedAccounts_QueryString now asserts plural query keys
  (user_ids, auth_config_ids, connected_account_ids, statuses) and
  explicitly guards that the legacy singular keys do not leak.
- TestExecuteTool_Success decodes the body as a raw map and asserts the
  wire key is `version` (not `toolkit_versions`).
- New TestExecuteToolRequest_VersionSerialization locks the json tag.
- New TestNewClient_HonorsInjectedHTTPClient drives a request through a
  recordingTransport and asserts the inbound *http.Client actually
  handled it.

Verification
- go test ./pkg/composio/... -cover -> 85.1% (38 tests; was 82.2% / 36).
- go vet, go build, gofmt -l all clean.

Refs: PR #4603 review, MUL-3720
Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-26 15:07:47 +08:00

160 lines
6.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# composio
A small, standalone Go SDK for the [Composio v3.1 REST API](https://docs.composio.dev/api-reference).
This package is intentionally self-contained — its only third-party dependency
is [`github.com/go-resty/resty/v2`](https://github.com/go-resty/resty). It does
not import any other Multica package, so it can be reused by other services or
extracted into its own module unchanged.
## Scope (MVP)
Only the endpoints required by the first-stage Composio integration are wired
up. More surface (auth configs, triggers, proxy execute, etc.) can be added
later without changing existing types.
| Capability | Method | REST endpoint |
| --- | --- | --- |
| Create Connect Link (hosted auth flow) | `Client.CreateLink` | `POST /connected_accounts/link` |
| Create MCP / tool-router session | `Client.CreateSession` | `POST /tool_router/session` |
| List connected accounts (per user) | `Client.ListConnectedAccounts` | `GET /connected_accounts` |
| Revoke a connection at the provider | `Client.RevokeConnection` | `POST /connected_accounts/{id}/revoke` |
| Delete a connection record (idempotent) | `Client.DeleteConnectedAccount` | `DELETE /connected_accounts/{id}` |
| List toolkits | `Client.ListToolkits` | `GET /toolkits` |
| Get a toolkit by slug | `Client.GetToolkit` | `GET /toolkits/{slug}` |
| Execute a tool deterministically | `Client.ExecuteTool` | `POST /tools/execute/{slug}` |
| Verify a webhook delivery | `VerifyWebhook` / `VerifyHTTPRequest` | (offline) |
## Quick start
```go
import (
"context"
"os"
"github.com/multica-ai/multica/server/pkg/composio"
)
client, err := composio.NewClient(composio.Options{
APIKey: os.Getenv("COMPOSIO_API_KEY"),
})
if err != nil { /* ... */ }
// 1. Send a user to the hosted Connect Link
link, err := client.CreateLink(ctx, composio.CreateLinkRequest{
AuthConfigID: "ac_xxxxxxxx", // configured in the Composio dashboard
UserID: multicaUserID.String(), // your own user id
CallbackURL: "https://app.multica.ai/api/integrations/composio/callback",
})
// → http.Redirect(w, r, link.RedirectURL, http.StatusFound)
// 2. After Composio creates the account, fetch what the user has connected
accounts, err := client.ListConnectedAccounts(ctx, composio.ListConnectedAccountsRequest{
UserIDs: []string{multicaUserID.String()},
Statuses: []string{"ACTIVE"},
})
// 3. Open an MCP session for the agent runtime
session, err := client.CreateSession(ctx, composio.CreateSessionRequest{
UserID: multicaUserID.String(),
ManageConnections: &composio.ManageConnections{
CallbackURL: "https://app.multica.ai/settings/integrations",
},
})
mcpURL := session.MCP.URL
mcpHdr := client.MCPAuthHeaders() // {"x-api-key": "..."} attach to MCP client
// 4. Disconnect (idempotent — 404 returns nil)
_ = client.RevokeConnection(ctx, "ca_xxxxxxxx")
_ = client.DeleteConnectedAccount(ctx, "ca_xxxxxxxx")
```
## Webhook verification
```go
secret := os.Getenv("COMPOSIO_WEBHOOK_SECRET")
http.HandleFunc("/api/integrations/composio/webhook", func(w http.ResponseWriter, r *http.Request) {
body, err := composio.VerifyHTTPRequest(secret, r, composio.VerifyOptions{})
if err != nil {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
event, err := composio.ParseEvent(body)
if err != nil {
http.Error(w, "bad payload", http.StatusBadRequest)
return
}
switch event.Type {
case "composio.connected_account.expired":
// mark row as expired, notify the user, ...
}
w.WriteHeader(http.StatusOK)
})
```
`VerifyWebhook` enforces a 300-second replay tolerance by default (matching
Composio's official SDKs). Pass `VerifyOptions{Tolerance: ...}` to tune it, or
`-1` to disable the check entirely (only useful when replaying historical
deliveries in tests).
The `webhook-signature` header is parsed as a list of `<version>,<sig>` pairs
so future signing versions don't break verification.
## Errors
All non-2xx responses are returned as a `*composio.APIError` carrying the
upstream status, slug, and message:
```go
_, err := client.CreateLink(ctx, req)
var apiErr *composio.APIError
if errors.As(err, &apiErr) {
if apiErr.IsRateLimited() { /* back off */ }
log.Printf("composio: %d %s (%s) req=%s", apiErr.HTTPStatus, apiErr.Message, apiErr.Slug, apiErr.RequestID)
}
```
`DeleteConnectedAccount` deliberately swallows 404 so the operation is
idempotent — every other error is propagated unchanged.
## Testing
The SDK is exercised entirely against `httptest.NewServer` so unit tests run
offline. Run them with:
```
go test ./server/pkg/composio/...
```
Current coverage: **82.2 %**.
## Design notes
- **Standalone.** Zero coupling to Multica internals — depend on this package
from `server/internal/integrations/composio` (Stage 2 integration glue) or
anywhere else without circular-import risk.
- **`x-api-key`, not Bearer.** Composio's v3.1 REST API authenticates with an
`x-api-key` header. The SDK sets it on every request and exposes
`Client.APIKeyHeader()` / `Client.MCPAuthHeaders()` so callers know
which header to attach when they're reaching Composio outside the SDK
(e.g. the MCP streaming client in the agent runtime).
- **Loose typing for evolving fields.** Session request blocks (`toolkits`,
`auth_configs`, `tools`, `multi_account`, …) and tool execution arguments
use `map[string]any` because their nested schemas are large and likely to
evolve. The frequently-used `manage_connections` block has a typed
wrapper — extend the typed surface as more shapes stabilise.
- **Webhook signing matches the official SDKs.** HMAC-SHA256 over
`{id}.{timestamp}.{rawBody}`, base64-encoded, with a 300-second replay
window. See
[Composio webhook verification](https://docs.composio.dev/docs/setting-up-triggers/subscribing-to-events#verifying-signatures).
## Roadmap (out of scope for v1)
- Auth-config CRUD (`/auth_configs`)
- Triggers (`/triggers`)
- Proxy execute (`/tools/execute/proxy`)
- Session meta-tool / `attach` / `search` endpoints
- Pagination iterators
- Built-in retry middleware on 429 / 5xx