Documentation
¶
Overview ¶
Package config loads, normalizes, and validates runtime configuration for the GitLab MCP server.
Configuration comes from environment variables, .env files, and CLI flags in cmd/server. The package centralizes defaults and bounds for stdio mode, HTTP mode, OAuth token verification, upload limits, safe mode, read-only mode, rate limiting, tool surfaces, capability surfaces, and meta-tool schema detail.
Validation Model ¶
Loaders keep user-facing configuration forgiving while preserving hard bounds that protect runtime behavior: URL values are parsed and normalized, duration and size fields are clamped to documented limits, and invalid enum values are reported before server registration begins.
Index ¶
- Constants
- func DeprecatedEnvWarnings() []string
- func EffectiveCapabilitySurface(capabilitySurface string) string
- func EffectiveToolSurface(toolSurface string) string
- func Getenv(name string) string
- func IsLoopbackGitLabURL(raw string) bool
- func LegacyEnvName(name string) string
- func ParseCSV(s string) []string
- func ParseTierFlag(value string) (tier edition.Tier, explicit bool, err error)
- func ParseToolSurface(toolSurfaceValue string) (string, error)
- func PrefixedEnvNames() []string
- func TierFromEnv() (tier edition.Tier, explicit bool, err error)
- func TrimmedGetenv(name string) string
- func UploadMaxFileSizeFromEnv() (int64, error)
- func ValidateMetadataURL(flag, raw string) error
- func ValidateOAuthGitLabURL(raw string) error
- func ValidatePublicURL(raw string) error
- type Config
- type EnvFileReport
- type HTTPEnvOverlay
- type ServerConfig
Constants ¶
const ( DefaultMaxFileSize = 2 * 1024 * 1024 * 1024 // 2 GB MaxFileSize = 1024 * 1024 * 1024 * 1024 // 1 TB upper bound )
DefaultMaxFileSize and MaxFileSize define the default and upper bound for GitLab upload payload sizes.
const ( DefaultMaxHTTPClients = 100 DefaultSessionTimeout = 30 * time.Minute DefaultRevalidateInterval = 15 * time.Minute DefaultPoolIdleTimeout = 1 * time.Hour // DefaultActionTimeout bounds one action's handler: it ends one that would // otherwise park until its client gave up, and never cuts a legitimate // call. It is the longest wait any action offers, a pipeline wait's // 3600-second ceiling, plus five minutes for the calls around it, since // the deadline starts before the handler does and a default equal to the // wait would end it a moment before it returned on its own. toolutil pins // the inequality in a test, since this package cannot import the constant // it must exceed. DefaultActionTimeout = 65 * time.Minute // DefaultDrainDelay is zero: on SIGTERM the listener closes at once, as // it always has. A deployment behind a balancer that polls /health sets // it to at least one probe interval, so the 503 the endpoint answers // while draining is seen before the close is. DefaultDrainDelay = 0 * time.Second MaxHTTPClients = 10000 MaxSessionTimeout = 24 * time.Hour MaxRevalidateInterval = 24 * time.Hour MaxPoolIdleTimeout = 24 * time.Hour MaxActionTimeout = 24 * time.Hour // MaxDrainDelay caps the announcement: a delay longer than this holds a // stopping process open past what any supervisor waits. MaxDrainDelay = 5 * time.Minute )
HTTP pool defaults.
const ( DefaultOAuthCacheTTL = 15 * time.Minute MinOAuthCacheTTL = 1 * time.Minute MaxOAuthCacheTTL = 2 * time.Hour )
OAuth defaults.
const ( DefaultRateLimitBurst = 40 DefaultHTTPRateLimitRPS = 10 MaxRateLimitRPS = 1000 MaxRateLimitBurst = 10000 )
DefaultRateLimitBurst is the bucket size used when rps > 0 and the operator did not set RATE_LIMIT_BURST explicitly.
DefaultHTTPRateLimitRPS is the tool-call limit an HTTP deployment gets unless the operator says otherwise. The specification requires a server exposing tools to rate limit their invocation, and an HTTP deployment is the shared one: every call it forwards is charged to its own egress address, so one looping client's volume lands on every other tenant and on the instance's limits. The number itself is a judgement call, not a spec value — far above any human-driven session, and still a bound on a retry loop. Stdio keeps 0: a single-user local process has no co-tenant to protect, so a limiter there only costs latency. Explicit 0 remains the opt-out in both.
const ( // MetaParamSchemaOpaque keeps the legacy `params: object` envelope. // This is the default and produces the smallest tools/list payload. MetaParamSchemaOpaque = "opaque" // MetaParamSchemaCompact emits a discriminated `oneOf` per action with // descriptions and $defs stripped to reduce size. MetaParamSchemaCompact = "compact" // MetaParamSchemaFull emits a discriminated `oneOf` per action with the // complete reflected JSON Schema for each action's params. MetaParamSchemaFull = "full" // DefaultMetaParamSchema is the default mode applied when neither the // META_PARAM_SCHEMA env var nor the --meta-param-schema flag is set. DefaultMetaParamSchema = MetaParamSchemaOpaque )
Meta-tool param schema modes.
const ( // ToolSurfaceMeta exposes the current domain meta-tool catalog. ToolSurfaceMeta = "meta" // ToolSurfaceIndividual exposes the full individual tool catalog. ToolSurfaceIndividual = "individual" // ToolSurfaceDynamic exposes the default low-token find/execute catalog. ToolSurfaceDynamic = "dynamic" // DefaultToolSurface selects the low-token find/execute catalog by default. DefaultToolSurface = ToolSurfaceDynamic )
Tool surface modes select which tool catalog is exposed by the server.
const ( // AuthModeLegacy accepts a GitLab personal access token supplied per // request, in either the PRIVATE-TOKEN header or Authorization: Bearer. AuthModeLegacy = "legacy" // AuthModeOAuth accepts only Authorization: Bearer, verified against the // GitLab instance, and advertises discovery through RFC 9728. AuthModeOAuth = "oauth" // DefaultAuthMode preserves the static-token behavior for deployments // that set no mode at all. DefaultAuthMode = AuthModeLegacy )
Authentication modes select how HTTP mode authenticates a request.
const ( // CapabilitySurfaceFull exposes the current resource and prompt catalog. CapabilitySurfaceFull = "full" // CapabilitySurfaceMinimal exposes only capabilities required for dynamic use. CapabilitySurfaceMinimal = "minimal" // DefaultCapabilitySurface preserves the existing resource and prompt catalog. DefaultCapabilitySurface = CapabilitySurfaceFull )
Capability surface modes select which non-tool MCP capabilities are exposed.
const DefaultGitLabURL = "https://gitlab.com"
DefaultGitLabURL is the GitLab instance used when GITLAB_URL is unset.
const DefaultSocketMode os.FileMode = 0o660
DefaultSocketMode is the permission mode a unix listening socket is created with: owner and group may connect, nobody else. A reverse proxy therefore reaches the server by sharing a group with it, which is the grant an operator makes deliberately — 0666 would let every local account reach an endpoint whose whole point is that it is not exposed.
const EnvFileName = ".gitlab-mcp-server.env"
EnvFileName is the name of the env file this server reads credentials from, in the user's home directory.
const EnvFileVar = "GITLAB_MCP_ENV_FILE"
EnvFileVar names a dotenv file to load in addition to the home file. It is read from the process environment only, which is what makes it an opt-in: a file this server declines to load cannot nominate itself.
It exists so the convenience the working-directory load used to provide survives as a deliberate act. A developer who keeps credentials in a repository-local file names that file, by absolute path, in the client configuration or the shell that launches the server, and says so.
The absolute path is the recommendation and not a formality. A relative value is resolved against the working directory, which the MCP client chooses and changes with every workspace it opens, so one relative line in a user-level client configuration nominates a different file in every repository the developer later opens. That is the working-directory load again, and it is why a relative value is announced as one at startup. A relative value belongs in a launcher that exists for a single workspace.
const EnvPrefix = "GITLAB_MCP_"
EnvPrefix is what every variable this project defines is named with from 2.8.0.
A stdio MCP server runs in whatever shell its client was started from, alongside every other tool that person uses. Names like RATE_LIMIT_RPS, AUTH_MODE and LOG_LEVEL are generic enough that another tool may already own them there, and a collision is silent: the server reads a value it was never given and behaves in a way nobody configured.
Variables ¶
This section is empty.
Functions ¶
func DeprecatedEnvWarnings ¶
func DeprecatedEnvWarnings() []string
DeprecatedEnvWarnings returns one line per unprefixed variable that was actually read, in a stable order, ready to be logged at startup.
It reports what was read rather than what is set, so an operator is never warned about a variable this deployment ignores anyway.
func EffectiveCapabilitySurface ¶
EffectiveCapabilitySurface returns the canonical capability surface.
func EffectiveToolSurface ¶
EffectiveToolSurface returns the canonical tool surface of a configuration snapshot: the surface it names, or the default when it names none.
func Getenv ¶
Getenv reads a setting under its prefixed name, falling back to the unprefixed one and recording that it did.
The prefixed name wins when both are set, and that case is recorded too: an operator who set both has one of them doing nothing, which is worth saying out loud rather than resolving in silence.
A name outside [prefixedNames] is read verbatim, so this is safe to use for GITLAB_URL, GITLAB_TOKEN and anything else that never gained a prefix.
func IsLoopbackGitLabURL ¶
IsLoopbackGitLabURL reports whether a GitLab base URL names this machine.
func LegacyEnvName ¶
LegacyEnvName returns the spelling a setting answered to before it gained EnvPrefix: the bare suffix for most, a GITLAB_-prefixed name for the ones that already had one.
func ParseTierFlag ¶
ParseTierFlag resolves a CLI --tier flag value into a tier and an "explicit" flag, mirroring [parseTierEnv]. It is exported for cmd/server HTTP-mode flag handling.
func ParseToolSurface ¶
ParseToolSurface resolves a TOOL_SURFACE value into a canonical tool surface, answering the default when nothing was set.
func PrefixedEnvNames ¶
func PrefixedEnvNames() []string
PrefixedEnvNames returns the variables that answer to both spellings, for tests and for documentation generators that must not drift from this list.
func TierFromEnv ¶
TierFromEnv resolves the tier the way Load does, from GITLAB_MCP_TIER, and reports whether it named one.
Exported for the one caller that needs this setting without the rest of a configuration: --tool-search inspects the catalog offline, so it must not demand the GitLab URL and token Load validates.
func TrimmedGetenv ¶
TrimmedGetenv is Getenv with surrounding whitespace removed, which is what almost every caller here wants from a value a human typed into a shell.
func UploadMaxFileSizeFromEnv ¶
UploadMaxFileSizeFromEnv resolves the upload limit from the environment, or returns the default when it is unset.
Exported because HTTP mode builds its configuration from flags rather than through Load, and the flag for this setting works by writing the environment variable. Without one function both paths call, the limit applied depended on the transport: raised on stdio, ignored on HTTP.
func ValidateMetadataURL ¶
ValidateMetadataURL checks a URL this deployment publishes in its protected-resource metadata.
These are links a consent screen or a directory follows, so a value that is not an absolute https URL produces a document clients reject or render with a link nobody can use — and it fails at that point rather than at startup, where an operator would see it. The flags say https; this is what makes that true.
http is allowed on a loopback host, matching --public-url, so a developer can point these at a local page while trying things out.
func ValidateOAuthGitLabURL ¶
ValidateOAuthGitLabURL requires the GitLab instance URL to use https in oauth mode. Bearer tokens are forwarded upstream on every call, so a cleartext instance URL would put a live credential on the wire (CWE-319). http is tolerated only for loopback hosts, matching the exemption ValidatePublicURL makes for local development.
func ValidatePublicURL ¶
ValidatePublicURL enforces the RFC 9728 constraints on the advertised protected-resource identifier. Exported for the HTTP flag path, which assembles its Config directly instead of going through Load.
Types ¶
type Config ¶
type Config struct {
GitLabURL string
// GitLabURLs is the full set of instances an HTTP deployment publishes,
// in the order they were configured; GitLabURL is its first entry.
//
// More than one turns the per-request GITLAB-URL header from a free
// choice into a selection among published instances, which is what makes
// it safe in oauth mode: the server validates the bearer token against
// the instance it is about to use, so a caller naming a host of their own
// would be handed the token. Empty or single-valued behaves exactly as
// GitLabURL alone always has.
GitLabURLs []string
GitLabToken string
SkipTLSVerify bool
DisableRetries bool // Disable GitLab client retries for unit tests.
ToolSurface string
CapabilitySurface string
// Tier is the resolved GitLab licensing tier for this configuration.
// When TierExplicit is false it holds the conservative default
// (edition.Free); callers detect the real tier from the instance license.
Tier edition.Tier
// TierExplicit reports whether the tier was set explicitly via GITLAB_MCP_TIER
// (stdio) or --tier (HTTP). When true, no instance license check is
// performed and Tier is used verbatim. When false, the tier is detected
// per instance, falling back to edition.Free.
TierExplicit bool
ReadOnly bool
SafeMode bool
EmbeddedResources bool // Append EmbeddedResource content blocks to get_* tool results (default true)
UploadMaxFileSize int64
MaxHTTPClients int // Maximum unique tokens in the server pool (HTTP mode only)
SessionTimeout time.Duration // Idle MCP session timeout (HTTP mode only)
RevalidateInterval time.Duration // Token re-validation interval (HTTP mode only)
// ActionTimeout bounds one action's handler in both transports: a call
// still running after this long is cancelled and answered with the
// deadline error. 0 disables the bound.
ActionTimeout time.Duration
// DrainDelay is how long, after SIGTERM, the HTTP listener stays open
// answering /health with 503 draining before it closes, so a balancer
// takes the instance out of rotation first. 0 closes at once.
DrainDelay time.Duration
// PoolIdleTimeout is how long a pooled per-token-and-URL server entry may go unused
// before it is reclaimed; 0 keeps entries until the pool's size bound
// evicts them (HTTP mode only).
PoolIdleTimeout time.Duration
// Stateless enables sessionless streamable HTTP (SEP-2567, protocol
// 2026-07-28): the server neither reads nor sets Mcp-Session-Id, every
// POST is self-contained, and GET/DELETE return 405 (HTTP mode only).
Stateless bool
// JSONResponse returns application/json bodies instead of
// text/event-stream (SSE) for streamable responses (HTTP mode only).
JSONResponse bool
// MaxRequestBodyBytes caps the size of incoming streamable HTTP request
// bodies. 0 uses the SDK default (4 MiB); negatives are rejected at
// validation (HTTP mode only).
//
// The default stays at the SDK's 4 MiB rather than being cut to 256 KiB or
// 1 MiB, which a hardening review proposed against the cost of parsing a
// deeply nested body. That defense now lives where the shape of the attack
// is, in the inbound JSON depth cap: a smaller byte budget only narrows the
// window, since a quarter of a megabyte still spells a hundred thousand
// levels of nesting, and a wide-but-shallow body of the full size
// unmarshals in tens of milliseconds either way. Cutting it would break the
// documented inline-upload path, where content_base64 is the only way a
// remote caller can send a file at all, for every deployment that never
// configured the flag. An operator who wants a smaller ceiling still has
// the flag.
MaxRequestBodyBytes int64
// TLSCertFile and TLSKeyFile enable TLS on the listener itself, for a
// deployment where the proxy does not share a machine with the server
// and the hop between them crosses a network. They are set together or
// not at all (HTTP mode only).
TLSCertFile string
TLSKeyFile string
// SocketMode is the permission bits applied to a unix socket named by
// the listen address. 0 means the default, [DefaultSocketMode].
SocketMode os.FileMode
AuthMode string // Auth mode for HTTP: "legacy" (default) or "oauth"
OAuthCacheTTL time.Duration // OAuth token cache TTL (HTTP mode, oauth auth mode)
// OAuthClientUIDs pins the OAuth applications whose tokens this
// deployment admits, by GitLab application uid. Empty — the default —
// admits any credential the instance accepts.
//
// It is the only recipient check available: GitLab's authorization server
// publishes no resource_indicators_supported, so RFC 8707 audience
// restriction cannot be used, and the specification's "or otherwise verify
// that they are the intended recipient" is met by comparing the
// application a token was minted for. It is a set because --gitlab-url is
// repeatable and every published instance has its own application.
//
// Off by default because turning it on refuses personal access tokens
// outright: a PAT belongs to no application, and it is a supported
// credential here.
OAuthClientUIDs []string
// PublicURL is the externally reachable origin of this deployment
// (scheme://host[:port][/path], no trailing slash). Required in oauth
// mode: RFC 9728 defines the protected-resource identifier as an https
// URL, and deriving it from the bind address produces host-less or
// wrong-origin identifiers behind any TLS-terminating proxy.
PublicURL string
// ResourceDocumentation is the RFC 9728 resource_documentation URL the
// protected-resource metadata advertises. Empty means this project's
// own OAuth setup guide. An operator running their own deployment
// points it at a page describing their own OAuth application, which is
// the only sanctioned way to lead a client to a client ID: RFC 9728
// defines no field for one.
ResourceDocumentation string
// ResourcePolicyURI and ResourceTermsURI are the RFC 9728
// resource_policy_uri and resource_tos_uri fields. Both are omitted from
// the metadata document when empty, which is the default: they describe a
// specific deployment's undertakings about the data reached with the
// tokens it accepts, and publishing a link to a page that does not exist
// would put a dead link on a consent screen.
ResourcePolicyURI string
ResourceTermsURI string
TrustedProxyHeader string // HTTP header with real client IP (e.g. X-Forwarded-For, X-Real-IP)
TrustedProxies []string // Addresses or CIDR ranges of the proxies TrustedProxyHeader is believed from; required with it
// TrustedOrigins are absolute origins (scheme://host[:port]) allowed to
// make cross-origin browser requests. Empty by default: the server
// refuses every cross-origin browser POST, and only a listed origin (or
// the PublicURL origin, seeded automatically) is exempted. A trusted
// origin is validation, not a bypass — the DNS-rebinding MUST is still
// met for every origin not on the list.
TrustedOrigins []string
ExcludeTools []string // Tool names to exclude from registration (comma-separated via EXCLUDE_TOOLS)
IgnoreScopes bool // When true, skip PAT scope detection and register all tools
RateLimitRPS float64 // Per-server tools/call rate limit in requests/second (0 = disabled)
RateLimitBurst int // Token-bucket burst size when RateLimitRPS > 0
// MetaParamSchema controls how meta-tool input schemas advertise the
// shape of the `params` object. Allowed values: "opaque" (default),
// "compact", "full". See [DefaultMetaParamSchema] and constants.
MetaParamSchema string
}
Config holds all configuration values for the MCP server.
func Load ¶
Load reads configuration from environment variables, after populating that environment from the dotenv files LoadEnvFiles accepts: the file EnvFileVar names and ~/.gitlab-mcp-server.env, in that order, neither of them overwriting a variable the client already passed.
func (*Config) Enterprise ¶
Enterprise reports whether the resolved tier is an Enterprise (Premium or Ultimate) tier. It derives the legacy binary "enterprise" notion from the 3-tier model so positional gating continues to behave identically.
func (*Config) InstanceURLs ¶
InstanceURLs returns the GitLab instances this configuration publishes.
GitLabURLs is the full list and GitLabURL its first entry, but only the flag layer fills both. Every other constructor — a test, a stdio load, any caller that predates the list — sets GitLabURL alone, and reading the slice directly there yields "no instance fixed", which resolves to the public GitLab and sends the request somewhere nobody asked for. Deriving one from the other here means the two cannot disagree.
func (*Config) ServerConfig ¶
func (c *Config) ServerConfig() *ServerConfig
ServerConfig returns the server-scoped subset of Config. Callers may enrich the returned snapshot with detected per-principal data before creating a concrete MCP server instance.
type EnvFileReport ¶
type EnvFileReport struct {
// ExplicitPath is the absolute path of the file EnvFileVar named, whether
// or not it could be read. Empty when the variable is unset.
ExplicitPath string
// ExplicitErr is why the file EnvFileVar named could not be read.
ExplicitErr error
// ExplicitRelative records that EnvFileVar named a relative path, so the
// file that was loaded is whichever one sits in the working directory the
// client chose. Meaningless when ExplicitPath is empty.
ExplicitRelative bool
// HomePath is the home file that was loaded. Empty when there is none.
HomePath string
// IgnoredPath is the absolute path of a working-directory .env that was
// found and deliberately not loaded. Empty when there is none.
IgnoredPath string
// IgnoredKeys are the variable names that file would have set, sorted.
IgnoredKeys []string
}
EnvFileReport describes what LoadEnvFiles did, so a caller can render it itself instead of parsing logs. LoadEnvFiles already announces the security-relevant parts.
func LoadEnvFiles ¶
func LoadEnvFiles() EnvFileReport
LoadEnvFiles populates the process environment from the dotenv files this server reads, and is safe to call more than once.
It is separate from Load because something has to consult these values before a full configuration exists. The startup path decides whether to show a first-run screen instead of serving, and that decision must be made against the same environment Load will see: a deployment that put its credentials in ~/.gitlab-mcp-server.env is configured, and a check reading only os.Getenv would conclude the opposite and refuse to start.
Every load is best-effort, a missing file being the normal case, and godotenv never overwrites a variable that is already set. The resulting precedence, highest first, is:
- the process environment, which is what the MCP client passed;
- the file EnvFileVar names, if any;
- ~/.gitlab-mcp-server.env.
A .env in the working directory is not on that list. When one exists it is read far enough to name its keys and reported at WARN, and nothing it contains reaches the environment.
type HTTPEnvOverlay ¶
type HTTPEnvOverlay struct {
GitLabURL *string
SkipTLSVerify *bool
ToolSurface *string
CapabilitySurface *string
MetaParamSchema *string
Tier *edition.Tier
TierExplicit bool
ReadOnly *bool
SafeMode *bool
EmbeddedResources *bool
IgnoreScopes *bool
ExcludeTools *string
MaxHTTPClients *int
SessionTimeout *time.Duration
PoolIdleTimeout *time.Duration
RevalidateInterval *time.Duration
ActionTimeout *time.Duration
DrainDelay *time.Duration
AuthMode *string
PublicURL *string
TrustedOrigins *string
OAuthCacheTTL *time.Duration
OAuthClientUID *string
RateLimitRPS *float64
RateLimitBurst *int
}
HTTPEnvOverlay holds the HTTP-relevant settings found in the environment. A nil field means the variable was absent or empty, and the caller keeps whatever the flag layer resolved.
func LoadHTTPEnvOverlay ¶
func LoadHTTPEnvOverlay() (*HTTPEnvOverlay, error)
LoadHTTPEnvOverlay reads the environment variables that have an HTTP flag counterpart, validating each with the same parser and bounds the stdio path applies. An invalid value is an error rather than a silent fallback, so a typo in a deployment manifest fails at startup instead of quietly running with a default the operator did not choose.
type ServerConfig ¶
type ServerConfig struct {
GitLabURL string
ToolSurface string
CapabilitySurface string
// Tier is the resolved GitLab licensing tier for this pool entry. When the
// owning Config did not set the tier explicitly, the pool detects it per
// instance before building the server.
Tier edition.Tier
// TierExplicit mirrors Config.TierExplicit: when true the tier is used
// verbatim and the pool performs no per-instance license detection.
TierExplicit bool
ReadOnly bool
// ReadOnlyFromTokenScope records that ReadOnly was not asked for by the
// operator but derived from the credential: this token cannot write, so a
// read-only surface was built for it. The two causes need different words
// when a withheld action is asked for — "reauthorize with a wider scope"
// versus "this deployment does not write" — and only the first is
// something the caller can act on.
ReadOnlyFromTokenScope bool
SafeMode bool
ExcludeTools []string
TokenScopes []string
RateLimitRPS float64
RateLimitBurst int
MetaParamSchema string
// Stateless mirrors Config.Stateless. It reaches the server because a
// sessionless transport cannot carry a server-initiated notification
// outside an open request, which decides whether the legacy
// resources/subscribe path can be honored at all.
Stateless bool
}
ServerConfig is an immutable configuration snapshot used to build one MCP server instance for a specific GitLab URL and credential principal.
func (*ServerConfig) Enterprise ¶
func (s *ServerConfig) Enterprise() bool
Enterprise reports whether this server's resolved tier is an Enterprise (Premium or Ultimate) tier, deriving the legacy binary notion from the tier.