Go SDK for Keycard — OAuth 2.0 and MCP authentication.
go get github.com/keycardai/credentials-goImport the sub-package you need:
import "github.com/keycardai/credentials-go/oauth" // Pure OAuth 2.0 primitives
import "github.com/keycardai/credentials-go/mcp" // MCP-specific OAuth integrationNo MCP dependency. Use standalone for JWT operations, JWKS key discovery, token exchange, and OAuth metadata discovery.
- JWT signing/verification —
JWTSigner,JWTVerifier - JWKS keyring —
JWKSOAuthKeyringwith two-level caching and request deduplication - Token exchange —
TokenExchangeClient(RFC 8693) - Discovery —
FetchAuthorizationServerMetadata(RFC 8414)
Builds on oauth to provide server-side and client-side MCP authentication.
- Bearer auth middleware —
RequireBearerAuth(standardnet/httpmiddleware) - Token exchange orchestration —
AuthProvider,AccessContext - Application credentials —
ClientSecret,WebIdentity(RFC 7523),EKSWorkloadIdentity - Metadata endpoints —
AuthMetadataHandler(.well-knownendpoints)
mux := http.NewServeMux()
mux.Handle("/.well-known/", mcp.AuthMetadataHandler(
mcp.WithIssuer("https://your-zone.keycard.cloud"),
mcp.WithScopesSupported([]string{"mcp:tools"}),
))
protected := mcp.RequireBearerAuth(
mcp.WithRequiredScopes("mcp:tools"),
)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
info := mcp.AuthInfoFromRequest(r)
fmt.Fprintf(w, "Hello, %s!", info.ClientID)
}))
mux.Handle("GET /api/hello", protected)authProvider, _ := mcp.NewAuthProvider(
mcp.WithZoneURL("https://your-zone.keycard.cloud"),
mcp.WithApplicationCredential(mcp.NewClientSecret(clientID, clientSecret)),
)
handler := mcp.RequireBearerAuth(
mcp.WithRequiredScopes("mcp:tools"),
)(authProvider.Grant("https://api.github.com")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ac := mcp.AccessContextFromRequest(r)
token, err := ac.Access("https://api.github.com")
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
// Use token.AccessToken to call the GitHub API
fmt.Fprintf(w, "GitHub token: %s", token.AccessToken)
})))authProvider, _ := mcp.NewAuthProvider(
mcp.WithZoneURL("https://your-zone.keycard.cloud"),
mcp.WithApplicationCredential(mcp.NewClientSecret(clientID, clientSecret)),
)
ac := authProvider.ExchangeTokens(ctx, userBearerToken, "https://api.github.com")
if ac.HasErrors() {
log.Printf("Exchange failed: %v", ac.GetError())
}
token, _ := ac.Access("https://api.github.com")
// Use token.AccessTokenwebIdentity := mcp.NewWebIdentity(
mcp.WithServerName("my-mcp-server"),
mcp.WithStorageDir("./keys"),
)
authProvider, _ := mcp.NewAuthProvider(
mcp.WithZoneURL("https://your-zone.keycard.cloud"),
mcp.WithApplicationCredential(webIdentity),
)| Type | Auth Method | Use Case |
|---|---|---|
ClientSecret |
HTTP Basic Auth | Simple deployments with client_id/secret |
WebIdentity |
private_key_jwt (RFC 7523) |
Zero-secret deployments, auto-generates RSA keys |
EKSWorkloadIdentity |
Pod identity token | AWS EKS workloads |
The SDK uses Go-idiomatic error types. Use errors.As to check specific error types:
token, err := ac.Access("https://api.github.com")
if err != nil {
var rae *mcp.ResourceAccessError
if errors.As(err, &rae) {
// Resource token unavailable
}
}The AccessContext is a non-throwing result container — it never panics. Check status before accessing tokens:
ac := authProvider.ExchangeTokens(ctx, userToken, "res1", "res2")
switch ac.Status() {
case mcp.StatusSuccess:
// All resources exchanged successfully
case mcp.StatusPartialError:
// Some resources failed — check individually
case mcp.StatusError:
// Global error — no resources available
}Go modules are published by pushing git tags:
git tag v0.1.0
git push origin v0.1.0pkg.go.dev indexes automatically. To trigger manually:
GOPROXY=proxy.golang.org go list -m github.com/keycardai/credentials-go@v0.1.0