mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-17 07:09:53 +02:00
* 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>
160 lines
6.0 KiB
Markdown
160 lines
6.0 KiB
Markdown
# 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
|