Documentation
¶
Index ¶
Constants ¶
const DefaultBaseURL = "https://api.clockify.me/api/v1"
Variables ¶
This section is empty.
Functions ¶
func IsDevControlPlaneDSN ¶ added in v1.1.0
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
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
// Streamable HTTP session caps (per-replica and per-principal).
// 0 = disabled (matches legacy behaviour). When non-zero,
// streamSessionManager.create rejects initialize requests beyond
// the cap with HTTP 503 + Retry-After (replica cap) or HTTP 429
// + Retry-After (principal cap). See ADR/Wave 5 notes.
MaxSessionsPerReplica int
MaxSessionsPerPrincipal int
// 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 (Config) Fingerprint ¶ added in v0.6.0
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.
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
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
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.