config

package
v1.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 10, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Index

Constants

View Source
const DefaultBaseURL = "https://api.clockify.me/api/v1"

Variables

This section is empty.

Functions

func IsDevControlPlaneDSN added in v1.1.0

func IsDevControlPlaneDSN(dsn string) bool

IsDevControlPlaneDSN reports whether dsn names one of the dev-only control-plane backends. "memory" / "memory://" keep state in process memory; a bare path or "file://..." rewrites a JSON file on every mutation. Neither is correct for a multi-process production deployment of streamable_http.

This predicate is the single source of truth for the fail-closed guard in Load() and the defence-in-depth guard in internal/runtime.BuildStore. Both callers refuse to run streamable_http against a dev DSN unless MCP_ALLOW_DEV_BACKEND=1 is explicit — Load() catches it at config time so operators see the error on startup instead of at first request.

func IsHostedProfile added in v1.2.1

func IsHostedProfile(name string) bool

IsHostedProfile reports whether the given profile name is one of the hosted profiles whose strict gates apply (currently shared-service and prod-postgres). Exported so packages outside config (runtime, authn) can take the same posture decision without re-implementing the registry.

func ProfileNames added in v1.1.0

func ProfileNames() []string

ProfileNames returns the profile names in a stable sorted order, suitable for error messages and the EnvSpec Enum list.

func ValidateBaseURL added in v1.2.1

func ValidateBaseURL(raw string, opts ValidateBaseURLOptions) error

ValidateBaseURL enforces the URL-safety contract for Clockify base URLs across config-load and per-tenant credential resolution. Same behaviour for self-hosted profiles as the historical helper; hosted profiles refuse anything that isn't HTTPS regardless of loopback or the insecure escape hatch.

Types

type Config added in v0.6.0

type Config struct {
	// Clockify
	APIKey         string
	WorkspaceID    string
	BaseURL        string
	RequestTimeout time.Duration
	MaxRetries     int
	Insecure       bool
	Timezone       string

	// Profile is the resolved value of MCP_PROFILE for this load cycle
	// (empty when unset). Surfaces the deployment posture to packages
	// outside config (e.g. runtime/tenantRuntime, which needs to know
	// whether hosted-mode validation should apply to per-tenant
	// baseURLs). Read-only after Load; do not mutate.
	Profile string

	// MCP transport
	Transport       string
	AuthMode        string
	HTTPBind        string
	GRPCBind        string
	BearerToken     string
	AllowedOrigins  []string
	AllowAnyOrigin  bool
	StrictHostCheck bool
	// BehindHTTPSProxy lets the HTTP transports emit
	// Strict-Transport-Security on plaintext responses because a
	// trusted upstream proxy is terminating TLS for us. Wired from
	// MCP_BEHIND_HTTPS_PROXY=1.
	BehindHTTPSProxy   bool
	MaxMessageSize     int64
	MetricsBind        string
	MetricsAuthMode    string
	MetricsBearerToken string
	// HTTP admission limits are process-local app-layer guards for
	// streamable_http and legacy http. Hosted profiles enable them by
	// default to reject obvious auth-probe and SSE-hold abuse before
	// JSON-RPC dispatch; cross-replica hosted quotas still belong at
	// the gateway/load-balancer layer.
	HTTPRateLimitPerIP         int
	HTTPRateLimitPerPrincipal  int
	HTTPRateLimitGETPerSession int
	HTTPRequireProtocolVersion bool
	DefaultProtocolVersion     string

	// Enterprise shared-service
	ControlPlaneDSN string
	// ControlPlaneAuditCap is the max number of audit events retained
	// in the file-backed control-plane store. 0 keeps the historical
	// unbounded behaviour; non-zero enables FIFO eviction. Wired from
	// MCP_CONTROL_PLANE_AUDIT_CAP. Postgres deployments ignore this
	// field in favour of time-based retention (B2).
	ControlPlaneAuditCap int
	// ControlPlaneAuditRetention caps the age of retained audit
	// events. The B2 reaper calls Store.RetainAudit(ctx, retention)
	// on a one-hour ticker; events with `at < now - retention` are
	// removed from the backend. Zero disables retention. Wired from
	// MCP_CONTROL_PLANE_AUDIT_RETENTION; default 720h (30 days).
	ControlPlaneAuditRetention time.Duration
	SessionTTL                 time.Duration
	TenantClaim                string
	SubjectClaim               string
	DefaultTenantID            string
	OIDCIssuer                 string
	OIDCAudience               string
	OIDCJWKSURL                string
	OIDCJWKSPath               string
	// OIDCJWKSAllowPrivate permits an explicit MCP_OIDC_JWKS_URL to
	// target loopback/private/reserved addresses. Default false blocks
	// local-network and metadata-service SSRF probes caused by
	// misconfigured JWKS overrides. Wired from
	// MCP_OIDC_JWKS_ALLOW_PRIVATE=1.
	OIDCJWKSAllowPrivate bool
	// OIDCResourceURI is the canonical URI clients use to address this
	// MCP server. When set, every OIDC token must list this URI in its
	// audience claim — RFC 8707 / MCP OAuth 2.1 resource indicator
	// binding. Wired from MCP_RESOURCE_URI.
	OIDCResourceURI string
	// OIDCVerifyCacheTTL is the hard ceiling on cached OIDC verify
	// results. Larger values amortise the per-request verify cost but
	// extend the window before a revoked token is re-checked. Zero
	// selects the conservative 60s default baked into authn; values
	// outside [1s, 5m] are rejected at config load. Hosted profiles
	// clamp explicit values above 60s back to 60s so revocation cannot
	// drift past the hosted-service contract. Wired from
	// MCP_OIDC_VERIFY_CACHE_TTL.
	OIDCVerifyCacheTTL time.Duration
	// OIDCJWKSCacheTTL is the lifetime of the in-memory JWKS document
	// cache. Zero leaves authn at its 5-minute default; explicit values
	// are validated at Load() to lie in [1m, 24h]. Hosted services
	// that rotate keys frequently can shorten the window so a
	// rotation lands without process restart. Pairs with the F2
	// kid-miss-triggered refresh for in-flight rotation handling.
	// Wired from MCP_OIDC_JWKS_CACHE_TTL.
	OIDCJWKSCacheTTL time.Duration
	// OIDCStrict opts the server into stricter OIDC behaviour for
	// shared-service / hosted deployments. When true, config.Load
	// rejects oidc + (no audience + no resource URI) and the OIDC
	// authenticator rejects tokens missing an `exp` claim. Wired from
	// MCP_OIDC_STRICT=1.
	OIDCStrict bool
	// OIDCRequireKID rejects OIDC JWTs whose JOSE header omits kid.
	// Hosted profiles enable it by default so a single-key JWKS cannot
	// accept kid-less tokens through the compatibility fallback.
	OIDCRequireKID       bool
	DisableInlineSecrets bool
	// ExposeAuthErrors controls whether HTTP transports include detailed
	// authenticator failure reasons in unauthenticated client responses.
	// Default false returns a generic "authentication failed" description
	// while server-side logs retain the detailed reason for operators.
	ExposeAuthErrors bool
	// SanitizeUpstreamErrors controls whether tool errors returned to MCP
	// clients omit the upstream Clockify response body. Default false
	// (verbose, useful for local development); hosted profiles
	// (shared-service, prod-postgres) flip it to true so a 4xx response
	// body cannot leak per-tenant info across tenant boundaries. The
	// full APIError is always logged server-side regardless. Wired from
	// CLOCKIFY_SANITIZE_UPSTREAM_ERRORS=1, with the hosted-profile
	// default applied when the operator hasn't overridden it.
	SanitizeUpstreamErrors bool
	// WebhookValidateDNS, when true, makes CreateWebhook/UpdateWebhook
	// resolve the host and reject any reply containing a private,
	// reserved, link-local, or loopback IP. Default false (literal-IP
	// check only); hosted profiles flip it on so a hostname pointing
	// at 169.254.169.254 (cloud-metadata) or a private-range A record
	// can't turn the Clockify outbound webhook delivery into an SSRF
	// probe. Wired from CLOCKIFY_WEBHOOK_VALIDATE_DNS=1, with the
	// hosted-profile default applied when the operator hasn't overridden.
	WebhookValidateDNS bool
	// WebhookAllowedDomains is the per-deployment escape-hatch list of
	// hostnames that bypass the WebhookValidateDNS private-IP check.
	// Supplied as a comma-separated list via
	// CLOCKIFY_WEBHOOK_ALLOWED_DOMAINS; whitespace around each entry is
	// trimmed and empty entries are skipped. Each entry matches a host
	// either exactly (`webhook.example.com`) or as a leading-dot
	// suffix that anchors a full DNS label (`.example.com` matches
	// `webhook.example.com` and `api.eu.example.com` but NOT
	// `attacker.example.com.evil.com`). Empty list = no bypass.
	// Use case: split-horizon DNS where a known-trusted hostname
	// resolves to a private IP only on the control-plane network.
	// See docs/runbooks/webhook-dns-validation.md §4b.
	WebhookAllowedDomains []string
	// RequireTenantClaim, when true, makes the OIDC authenticator
	// reject any token whose tenant claim is absent — instead of
	// quietly falling back to MCP_DEFAULT_TENANT_ID. Wired from
	// MCP_REQUIRE_TENANT_CLAIM=1; the default fallback is preserved
	// for backward-compat with self-hosted single-tenant deployments.
	RequireTenantClaim   bool
	ForwardTenantHeader  string
	ForwardSubjectHeader string
	// RequireForwardTenantClaim, when true, makes forward_auth reject
	// requests missing the forward tenant header instead of falling back
	// to MCP_DEFAULT_TENANT_ID. Wired from
	// MCP_REQUIRE_FORWARD_TENANT_CLAIM=1; hosted profiles set it by
	// default so proxy header drift cannot collapse tenants.
	RequireForwardTenantClaim bool
	// ForwardAuthTrustedProxies is the parsed CIDR allow-list for
	// the forward_auth authenticator. The authenticator rejects any
	// request whose source address is not inside one of these networks
	// before reading X-Forwarded-User / X-Forwarded-Tenant. An empty
	// MCP_FORWARD_AUTH_TRUSTED_PROXIES value is allowed only on a
	// loopback bind and is narrowed to loopback CIDRs at config load.
	// Wired from MCP_FORWARD_AUTH_TRUSTED_PROXIES (comma-separated).
	ForwardAuthTrustedProxies []*net.IPNet
	MTLSTenantHeader          string
	// MTLSTenantSource selects how the mtls authenticator derives the
	// tenant identifier. "cert" (default) uses the verified client
	// certificate (URI SAN → Subject O fallback). "header" honours the
	// MTLSTenantHeader. "header_or_cert" tries the header first, then
	// the cert. Wired from MCP_MTLS_TENANT_SOURCE.
	MTLSTenantSource string
	// RequireMTLSTenant rejects authentication when the configured
	// tenant source(s) yield no tenant. Wired from
	// MCP_REQUIRE_MTLS_TENANT=1; default false preserves the
	// historical "fall back to MCP_DEFAULT_TENANT_ID" behaviour for
	// self-hosted single-tenant deployments.
	RequireMTLSTenant bool

	// Tool execution
	ToolTimeout               time.Duration
	ConcurrencyAcquireTimeout time.Duration

	// Dispatch-layer concurrency bound for stdio tools/call. 0 disables.
	MaxInFlightToolCalls int

	// Hard cap on entries aggregated by report tools. 0 disables.
	ReportMaxEntries int

	// DeltaFormat selects the resource notification diff algorithm.
	// "merge" (default) = RFC 7396 merge patch. "jsonpatch" = RFC 6902.
	DeltaFormat string

	// GRPCReauthInterval is how often long-lived gRPC streams re-validate
	// their auth token. 0 = disabled (per-stream validation only).
	GRPCReauthInterval time.Duration
	// GRPCPeerCIDRAllow optionally restricts gRPC calls to peers whose
	// source IP is inside one of these CIDRs. Empty preserves the default
	// behavior. Wired from MCP_GRPC_PEER_CIDR_ALLOW.
	GRPCPeerCIDRAllow []*net.IPNet

	// AuditDurabilityMode controls behavior when audit persistence fails for
	// a successful non-read-only tool call.
	// "best_effort" (default): log + metric; the call still reports success.
	// "fail_closed": the call returns an error so the client knows the audit
	// trail is incomplete. The mutation already happened; this prevents
	// silent untracked mutations when the intent record is not durable.
	// "fail_closed_strict": also returns an error when the post-mutation
	// outcome record cannot be persisted.
	AuditDurabilityMode string

	// HTTPInlineMetricsEnabled controls whether /metrics is mounted on the
	// main HTTP listener when MCP_TRANSPORT=http. Default: false (disabled).
	// The dedicated metrics listener (MCP_METRICS_BIND) is the preferred
	// pattern; this is a compatibility escape hatch that requires explicit
	// operator intent.
	HTTPInlineMetricsEnabled bool
	// HTTPInlineMetricsAuthMode governs auth for inline main-listener /metrics.
	// "inherit_main_bearer" (default when enabled): require the same bearer
	// token as the /mcp endpoint.
	// "static_bearer": require a separate MCP_HTTP_INLINE_METRICS_BEARER_TOKEN.
	// "none": unauthenticated — operator must opt in explicitly; startup warns.
	HTTPInlineMetricsAuthMode string
	// HTTPInlineMetricsBearerToken is the separate token for inline metrics
	// when HTTPInlineMetricsAuthMode == "static_bearer".
	HTTPInlineMetricsBearerToken string

	// HTTPLegacyPolicy governs startup behavior when MCP_TRANSPORT=http.
	// "warn" (default): emit structured startup warnings about legacy HTTP
	// limitations and recommend streamable_http.
	// "deny": refuse to start; operator must switch to streamable_http or
	// explicitly set MCP_HTTP_LEGACY_POLICY=allow.
	// "allow": permit startup without deny or warn behavior.
	HTTPLegacyPolicy string

	// GRPCTLSCert and GRPCTLSKey are paths to the server TLS cert and
	// private key for the gRPC transport. Both must be set together.
	GRPCTLSCert string
	GRPCTLSKey  string
	// HTTPTLSCert and HTTPTLSKey are paths to the server TLS cert and
	// private key for the streamable_http transport. Both must be set
	// together. Required when MCP_AUTH_MODE=mtls on streamable_http.
	// Legacy http (MCP_TRANSPORT=http) does not support TLS — see the
	// HTTPTLSCert validation in Load().
	HTTPTLSCert string
	HTTPTLSKey  string
	// MTLSCACertPath is the path to the CA cert for client certificate
	// verification. Required when MCP_AUTH_MODE=mtls on gRPC or
	// streamable_http.
	MTLSCACertPath string
}

func Load added in v0.6.0

func Load() (Config, error)

func (Config) Fingerprint added in v0.6.0

func (c Config) Fingerprint() map[string]any

type EnvSpec added in v1.0.1

type EnvSpec struct {
	Name         string
	Group        string
	Default      string
	Help         string
	Enum         []string
	AppliesTo    []string
	Deprecated   bool
	Replacement  string
	EssentialDoc bool
	Sensitive    bool
}

EnvSpec is the single source of truth for every environment variable the server honours. Help text (cmd/clockify-mcp/help_generated.go), the README essentials table, the config-doc-parity CI gate, and the default-parity test all derive from AllSpecs(). Adding a new Getenv in config.go without an entry here trips TestEnvSpec_CoversEveryGetenv.

func AllSpecs added in v1.0.1

func AllSpecs() []EnvSpec

AllSpecs returns the registry in declaration order. Order drives help output grouping; the README renderer sorts alphabetically by Name.

type Profile added in v1.1.0

type Profile struct {
	// Name is the stable identifier passed via --profile=<name> or
	// MCP_PROFILE=<name>. Hyphenated for CLI ergonomics.
	Name string

	// Summary is the one-line description shown in --help and by
	// the doctor subcommand. Keep under 80 characters.
	Summary string

	// Env is the map of env-var keys to the profile's default value.
	// Only unset keys receive a default; an operator who sets any of
	// these explicitly gets their value through. Keys MUST appear in
	// AllSpecs() — an un-specced key here trips the
	// TestProfile_KeysAreSpecced invariant.
	Env map[string]string
}

Profile is a named bundle of env-var defaults for a supported deployment shape. Applying a profile sets each of its Env keys only when the key is currently unset in the process environment; explicit values (from shell, container env, systemd EnvironmentFile, etc.) always win. Five canonical profile names are registered: local-stdio, single-tenant-http, shared-service, private-network-grpc, prod-postgres (the last is a shared-service alias with ENVIRONMENT=prod). docs/deploy/ carries a note per profile shape; prod-postgres is documented inside the shared-service note, and docs/deploy/profile-self-hosted.md is a legacy-shape upgrade pointer without a corresponding registered profile.

func AllProfiles added in v1.1.0

func AllProfiles() []Profile

AllProfiles returns a defensive copy of the profile registry in declaration order. Used by help output, the doctor subcommand, and the profile-keys parity test.

func ProfileByName added in v1.1.0

func ProfileByName(name string) (*Profile, error)

ProfileByName resolves a profile by its name. Returns an actionable error naming the valid choices when the name is unknown.

type ValidateBaseURLOptions added in v1.2.1

type ValidateBaseURLOptions struct {
	Hosted        bool
	AllowInsecure bool
}

ValidateBaseURLOptions controls how ValidateBaseURL interprets a candidate Clockify API URL. Hosted reflects whether the deployment is one of the multi-tenant profiles (shared-service, prod-postgres); hosted mode demands HTTPS unconditionally — no loopback bypass and no CLOCKIFY_INSECURE escape — so a tenant that supplies an http:// or loopback baseURL via control-plane credentials cannot smuggle cleartext traffic through a production gateway. AllowInsecure honours the operator's CLOCKIFY_INSECURE flag for self-hosted deployments where the operator owns both ends of the wire.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL