mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-09 09:56:47 +08:00
Standalone SDK for a self-hosted open-connector instance: provider catalog and setup, OAuth client configs, OAuth and API-key connection flows, connection deletion, and runtime token management. Responses are unwrapped per path family (/v1 envelope vs bare /api JSON); secrets in returned structs are redacted from String, GoString and slog output. Tests replay recorded responses from a real instance. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01XqqpKySn2iWtXriK3Gsgak
openconnector
A small, standalone Go SDK for the admin REST API of a self-hosted
open-connector server. Its only
third-party dependency is github.com/go-resty/resty/v2; it imports no other
Multica package. The MCP endpoint is intentionally not wrapped: agents connect
to /mcp directly with a runtime token (header Accept: application/json, text/event-stream, responses are single-event SSE).
Usage
c, err := openconnector.NewClient(openconnector.Options{
BaseURL: "http://127.0.0.1:3000",
AdminToken: os.Getenv("OOMOL_CONNECT_ADMIN_TOKEN"), // sent as Authorization: Bearer
})
providers, _ := c.ListProviders(ctx)
setup, _ := c.GetProviderSetup(ctx, "github")
oauth, _ := setup.FindAuth(openconnector.AuthTypeOAuth2)
req, _ := c.BeginOAuthConnect(ctx, "github", openconnector.BeginOAuthConnectRequest{
ReturnURI: "https://app.example.com/callback",
AuthorizationOptionIDs: oauth.DefaultAuthorizationOptionIDs(), // never omit, see below
})
st, _ := c.GetConnectionRequest(ctx, req.ConnectionRequestID) // poll, >= 2s apart
tok, _ := c.CreateRuntimeToken(ctx, openconnector.CreateRuntimeTokenRequest{
Name: "task-123", AllowedActions: []string{"github.*"},
AllowedConnections: []string{conn.ID},
})
defer c.DeleteRuntimeToken(ctx, tok.Record.ID)
Response shapes differ by path family
| Path family | Success | Failure |
|---|---|---|
/v1/* |
{"success":true,"data":...,"meta":{}} |
{"success":false,"message","errorCode","data":null} (HTTP status is usually non-2xx, but success=false on 2xx is also handled) |
/api/* |
bare JSON object or array | {"error":{"code","message"}} |
The client unwraps both and returns *APIError{Status, Code, Message} for every
failure. errors.Is(err, ErrNotFound | ErrUnauthorized | ErrForbidden) matches
by HTTP status. Errors never contain request headers or bodies.
Behaviors to know
GET /v1/providersis the catalog (iconUrlis mostly null). A provider can list several auth types; iterateProviderSetup.Authinstead of assuming one.GET /api/oauth/configsreturns every OAuth provider (about 118 KB); filter onConfiguredor useConfiguredOAuthServices.- Omitting
AuthorizationOptionIDsrequests all scopes, including destructive ones. PassDefaultAuthorizationOptionIDs()(required or default-selected, non-destructive). - A new
BeginOAuthConnectfor a service supersedes the pending request of the same service (old one becomesfailed/request_superseded); the connector has a single owner, so serialize connects per service. GetConnectionRequesttime fields mix epoch milliseconds (createdAt,updatedAt) and ISO strings (expiresAt); both becometime.Time.- API key connections get a random UUID name (
Connection.ConnectionName, JSONalias) andIsDefault=false; MCP calls must pass that name. DeleteConnectionis addressed by service + name and is idempotent (the server answers 200 even for unknown ones).DeleteRuntimeTokenis not: a repeated delete returnsErrNotFound.- Runtime token lists are always serialized, nil becoming
[], because omittingallowedTriggersresets it.allowedConnectionsIDs are not validated by the server. CreatedRuntimeToken.Tokenis shown once.String(),%+v,%#vand slog output redact it, as they do forOAuthConfigInputandAPIKeyConnectRequest.