mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-06 14:00:09 +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>
100 lines
4.3 KiB
Go
100 lines
4.3 KiB
Go
package composio
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
)
|
|
|
|
// --- Session creation ---------------------------------------------------
|
|
|
|
// CreateSessionRequest is the body of POST /tool_router/session.
|
|
//
|
|
// The minimum required field is [UserID]. Everything else is optional and
|
|
// maps directly to the v3.1 wire schema:
|
|
// https://docs.composio.dev/reference/api-reference/tool-router/postToolRouterSession
|
|
//
|
|
// The schema is intentionally typed loosely (map-based) for the nested
|
|
// `toolkits`, `auth_configs`, `tools`, `tags`, `multi_account`, etc. fields
|
|
// because they carry many child attributes and are expected to evolve.
|
|
// Callers can still construct strongly typed wrappers on top.
|
|
type CreateSessionRequest struct {
|
|
UserID string `json:"user_id"`
|
|
Toolkits map[string]any `json:"toolkits,omitempty"`
|
|
AuthConfigs map[string]any `json:"auth_configs,omitempty"`
|
|
ConnectedAccounts map[string]any `json:"connected_accounts,omitempty"`
|
|
ManageConnections *ManageConnections `json:"manage_connections,omitempty"`
|
|
Tools map[string]any `json:"tools,omitempty"`
|
|
Tags any `json:"tags,omitempty"`
|
|
Workbench map[string]any `json:"workbench,omitempty"`
|
|
MultiAccount map[string]any `json:"multi_account,omitempty"`
|
|
Preload map[string]any `json:"preload,omitempty"`
|
|
Search map[string]any `json:"search,omitempty"`
|
|
Execute map[string]any `json:"execute,omitempty"`
|
|
Experimental map[string]any `json:"experimental,omitempty"`
|
|
}
|
|
|
|
// ManageConnections is the typed flavor of the `manage_connections` object —
|
|
// the field used most often by integrations.
|
|
type ManageConnections struct {
|
|
Enable *bool `json:"enable,omitempty"`
|
|
CallbackURL string `json:"callback_url,omitempty"`
|
|
EnableWaitForConnections *bool `json:"enable_wait_for_connections,omitempty"`
|
|
EnableConnectionRemoval *bool `json:"enable_connection_removal,omitempty"`
|
|
}
|
|
|
|
// MCPDescriptor is the streamable HTTP entrypoint for the session's MCP.
|
|
type MCPDescriptor struct {
|
|
Type string `json:"type"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
// CreateSessionResponse mirrors the subset of the upstream response the SDK
|
|
// currently exposes typed. Additional fields can be added without breaking
|
|
// callers.
|
|
type CreateSessionResponse struct {
|
|
SessionID string `json:"session_id"`
|
|
MCP MCPDescriptor `json:"mcp"`
|
|
ToolRouterTools []string `json:"tool_router_tools,omitempty"`
|
|
Config map[string]any `json:"config,omitempty"`
|
|
ConfigVersion int `json:"config_version,omitempty"`
|
|
Experimental map[string]any `json:"experimental,omitempty"`
|
|
Warnings []SessionWarning `json:"warnings,omitempty"`
|
|
}
|
|
|
|
// SessionWarning is a non-fatal warning emitted at session creation time.
|
|
type SessionWarning struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
// CreateSession opens a new tool-router (a.k.a. MCP) session for the given
|
|
// user. The returned [CreateSessionResponse.MCP.URL] is the URL an
|
|
// MCP-compatible client connects to.
|
|
//
|
|
// Use [Client.MCPAuthHeaders] to obtain the matching headers — the SDK
|
|
// returns these separately rather than baking them into the response so
|
|
// that callers don't accidentally leak the secret API key through logs.
|
|
func (c *Client) CreateSession(ctx context.Context, req CreateSessionRequest) (*CreateSessionResponse, error) {
|
|
if req.UserID == "" {
|
|
return nil, errors.New("composio: CreateSession: UserID is required")
|
|
}
|
|
var out CreateSessionResponse
|
|
if err := c.do(c.newRequest(ctx).SetBody(req), http.MethodPost, "/tool_router/session", &out); err != nil {
|
|
return nil, err
|
|
}
|
|
return &out, nil
|
|
}
|
|
|
|
// MCPAuthHeaders returns the headers an MCP client must send when connecting
|
|
// to a session URL produced by [Client.CreateSession].
|
|
//
|
|
// Composio authenticates MCP streaming the same way it authenticates the
|
|
// REST API — with the project's `x-api-key` header. Keeping this as a
|
|
// dedicated helper makes it explicit at the call site that bearer
|
|
// material is leaving the SDK boundary, so callers can route it through
|
|
// their secret-redact pipeline (see server/pkg/redact).
|
|
func (c *Client) MCPAuthHeaders() map[string]string {
|
|
return c.APIKeyHeader()
|
|
}
|