toolutil

package
v3.0.0 Latest Latest
Warning

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

Go to latest
Published: Sep 10, 2026 License: MIT Imports: 42 Imported by: 0

Documentation

Overview

Package toolutil provides shared utilities for MCP tool handler sub-packages. It contains error handling, pagination, logging, text processing, Markdown formatting helpers, and meta-tool infrastructure used across all domain sub-packages under internal/tools/.

The package centralizes cross-cutting behavior for:

  • MCP response construction, annotations, icons, embedded resources, and Markdown formatter registration.
  • GitLab API support types for pagination, GraphQL cursors, access levels, diff positions, file validation, and flexible string-or-integer IDs.
  • Operational helpers for structured errors, not-found results, logging, rate limiting, polling, destructive-action confirmation, and identity resolution.
  • Schema middleware that hardens generated tool schemas and enriches common pagination parameters with numeric bounds.

This package must never import from domain sub-packages to prevent circular dependencies. The dependency direction is: domain sub-packages -> toolutil.

Common Entry Points

Handlers usually use WrapErr, WrapErrWithMessage, or WrapErrWithHint for errors; PaginationInput and PaginationOutput for paginated endpoints; StringOrInt for GitLab IDs that may be numeric or path-based; and RegisterMarkdown or RegisterMarkdownResult to publish type-specific Markdown renderers.

Destructive tools use ConfirmAction when they need an MCP elicitation step, and list/detail outputs commonly embed HintableOutput so meta-tools can add next-step guidance.

Markdown and Structured Output

Domain packages register Markdown renderers with RegisterMarkdown and RegisterMarkdownResult. The registry lets handlers return typed structured output for MCP clients while still producing compact human-readable Markdown. Shared helpers such as MarkdownTableHeader, MarkdownTableSeparator, and MarkdownTableRow keep table formatting consistent across domains.

Dependency Direction

The package dependency shape is intentionally one-way:

internal/tools/{domain}
    |
    v
toolutil
    |
    v
MCP SDK and GitLab client primitives

Index

Constants

View Source
const (
	ActionSpecContentList      = "list"
	ActionSpecContentDetail    = "detail"
	ActionSpecContentMutate    = "mutate"
	ActionSpecContentAssistant = "assistant"
	ActionSpecContentImage     = "image"

	ActionSpecNotFoundNone      = "none"
	ActionSpecNotFoundResult    = "not_found_result"
	ActionSpecNotFoundPropagate = "propagate_error"

	ActionSpecEmbeddedNone     = "none"
	ActionSpecEmbeddedOptional = "optional"
	ActionSpecEmbeddedAlways   = "always"

	ActionSpecRichStandard     = "standard"
	ActionSpecRichImage        = "image"
	ActionSpecRichResourceLink = "resource_link"
	ActionSpecRichMixed        = "mixed"
)
View Source
const (
	// ApproverAny matches merge requests with at least one approver.
	ApproverAny = "Any"
	// ApproverNone matches merge requests with no approvers.
	ApproverNone = "None"
)

Approver filter literals accepted in place of a list of user IDs.

View Source
const (
	DefaultMaxFileSize = config.DefaultMaxFileSize
	// ImportArchiveAllowlistEnv names extra directories allowed for local
	// GitLab project/group import archives, separated by the OS path-list separator.
	ImportArchiveAllowlistEnv = "GITLAB_MCP_ALLOWED_IMPORT_DIRS"
	// UploadDirAllowlistEnv names extra directories a tool may READ a local
	// file from (every file_path input), separated by the OS path-list
	// separator. The working directory and the OS temp directory are always
	// allowed.
	UploadDirAllowlistEnv = "GITLAB_MCP_ALLOWED_UPLOAD_DIRS"
	// DownloadDirAllowlistEnv names extra directories a tool may WRITE a
	// downloaded file into (output_path), separated by the OS path-list
	// separator. The working directory and the OS temp directory are always
	// allowed.
	DownloadDirAllowlistEnv = "GITLAB_MCP_ALLOWED_DOWNLOAD_DIRS"
)

DefaultMaxFileSize re-exports the upload size limit from config as the single source of truth for tool utilities.

View Source
const (
	GraphQLDefaultFirst = 20
	GraphQLMaxFirst     = 100
)

GraphQL pagination defaults.

View Source
const (
	RefusalSafeMode          = "safe_mode"
	RefusalNeedsConfirmation = "needs_confirmation"
	RefusalInvalidParams     = "invalid_params"
	RefusalUnknownAction     = "unknown_action"
	RefusalRateLimited       = "rate_limited"
)

Reasons a call was declined before its handler ran, recorded as the reason attribute of LogToolRefusal.

They are a closed set on purpose: an operator computing a refusal rate needs to group by something, and free text does not group.

View Source
const (
	DateFormatISO  = "2006-01-02"
	DateTimeFormat = "2006-01-02T15:04:05Z"
)

Common date format constants used across tool sub-packages.

View Source
const (
	TblRowID          = "| ID | %d |\n"
	TblRowStatus      = "| Status | %s |\n"
	TblRowCreatedAt   = "| Created At | %s |\n"
	TblRowUpdatedAt   = "| Updated At | %s |\n"
	TblRowHasFailures = "| Has Failures | %v |\n"
)

Table row format constants for common detail-table fields. Each pairs with TblFieldValue and is shared across sub-packages to avoid duplicated literals.

View Source
const (
	FmtMdH1 = "# %s\n\n"
	FmtMdH2 = "## %s\n\n"
	FmtMdH3 = "### %s\n\n"
	FmtMdH4 = "#### %s\n\n"
	FmtMdH5 = "##### %s\n\n"
	FmtMdH6 = "###### %s\n\n"
)

Headings format string with trailing blank line.

View Source
const (
	FmtMdID          = "- **ID**: %d\n"
	FmtMdName        = "- **Name**: %s\n"
	FmtMdTitle       = "- **Title**: %s\n"
	FmtMdState       = "- **State**: %s\n"
	FmtMdStatus      = "- **Status**: %s\n"
	FmtMdDescription = "- **Description**: %s\n"
	FmtMdPath        = "- **Path**: %s\n"
	FmtMdVisibility  = "- **Visibility**: %s\n"
	FmtMdEmail       = "- **Email**: %s\n"
	FmtMdUsername    = "- **Username**: %s\n"
	FmtMdTarget      = "- **Target**: %s\n"
	FmtMdCreated     = "- **Created**: %s\n"
	FmtMdUpdated     = "- **Updated**: %s\n"

	FmtMdAuthorAt    = "- **Author**: @%s\n"
	FmtMdAuthor      = "- **Author**: %s\n"
	FmtMdSectionText = "\n%s\n"
	FmtMdH2Count     = "## %s (%d)\n\n"
	TblSep1Col       = "| --- |\n"
	TblSep2Col       = "| --- | --- |\n"
	TblSep3Col       = "| --- | --- | --- |\n"
	TblSep4Col       = "| --- | --- | --- | --- |\n"
	TblSep5Col       = "| --- | --- | --- | --- | --- |\n"
	TblSep6Col       = "| --- | --- | --- | --- | --- | --- |\n"
	TblSep7Col       = "| --- | --- | --- | --- | --- | --- | --- |\n"
	TblSep8Col       = "| --- | --- | --- | --- | --- | --- | --- | --- |\n"
	TblSep9Col       = "| --- | --- | --- | --- | --- | --- | --- | --- | --- |\n"
	TblSep10Col      = "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n"
	FmtRow1Str       = "| %s |\n"
	FmtRow2Str       = "| %s | %s |\n"
	FmtRow3Str       = "| %s | %s | %s |\n"
	FmtRow4Str       = "| %s | %s | %s | %s |\n"
	FmtRow5Str       = "| %s | %s | %s | %s | %s |\n"
	FmtRow6Str       = "| %s | %s | %s | %s | %s | %s |\n"
	FmtRow7Str       = "| %s | %s | %s | %s | %s | %s | %s |\n"
	FmtRow8Str       = "| %s | %s | %s | %s | %s | %s | %s | %s |\n"
	FmtRow9Str       = "| %s | %s | %s | %s | %s | %s | %s | %s | %s |\n"
	FmtRow10Str      = "| %s | %s | %s | %s | %s | %s | %s | %s | %s | %s |\n"
)

Markdown format constants for repeated table separators and field patterns.

View Source
const (
	EmojiDraft        = "\U0001F4DD"   // 📝
	EmojiWarning      = "\u26A0\uFE0F" // ⚠️
	EmojiConfidential = "\U0001F512"   // 🔒
	EmojiArchived     = "\U0001F4E6"   // 📦
	EmojiStar         = "\u2B50"       // ⭐
	EmojiSuccess      = "\u2705"       // ✅
	EmojiCross        = "\u274C"       // ❌
	EmojiRefresh      = "\U0001F504"   // 🔄
	EmojiFile         = "\U0001F4C4"   // 📄
	EmojiFolder       = "\U0001F4C1"   // 📁
	EmojiCalendar     = "\U0001F4C5"   // 📅
	EmojiUpArrow      = "\u2B06\uFE0F" // ⬆️
	EmojiDownArrow    = "\u2B07\uFE0F" // ⬇️
	EmojiInfo         = "\u2139\uFE0F" // ℹ️
	EmojiQuestion     = "\u2753"       // ❓
	EmojiLink         = "\U0001F517"   // 🔗
	EmojiUser         = "\U0001F464"   // 👤
	EmojiGroup        = "\U0001F465"   // 👥
	EmojiPipeline     = "\U0001F6A7"   // 🚧
	EmojiMergeRequest = "\U0001F5C3"   // 🗃️
	EmojiIssue        = "\U0001F4A1"   // 💡
	EmojiRed          = "\U0001F534"   // 🔴
	EmojiYellow       = "\U0001F7E1"   // 🟡
	EmojiGreen        = "\U0001F7E2"   // 🟢
	EmojiProhibited   = "\U0001F6AB"   // 🚫
	EmojiWhiteCircle  = "\u26AA"       // ⚪
	EmojiParty        = "\U0001F389"   // 🎉
	EmojiPurple       = "\U0001F7E3"   // 🟣
	EmojiBlue         = "\U0001F535"   // 🔵
	EmojiStop         = "\u26D4"       // ⛔
	EmojiSkip         = "\u23ED\uFE0F" // ⏭️
	EmojiNew          = "\U0001F195"   // 🆕
	EmojiHand         = "\u270B"       // ✋
)

Contextual emoji constants for consistent visual indicators across formatters.

View Source
const (
	MetaParamSchemaOpaque  = "opaque"
	MetaParamSchemaCompact = "compact"
	MetaParamSchemaFull    = "full"
)

Meta-tool param schema mode constants. Mirrors the values accepted by the META_PARAM_SCHEMA env var and --meta-param-schema CLI flag in package config. Duplicated here to avoid an import cycle (config → toolutil → mcp).

View Source
const (
	PollMinInterval     = 5
	PollMaxInterval     = 60
	PollDefaultInterval = 10
	PollMinTimeout      = 1
	PollMaxTimeout      = 3600
	PollDefaultTimeout  = 300
)

Polling bounds and defaults (all values in seconds) for tools that wait on a GitLab resource to reach a terminal state (pipelines, jobs, deployments).

View Source
const DefaultMaxArgumentDepth = 64

DefaultMaxArgumentDepth is the nesting ceiling applied to a tools/call arguments object.

No schema this server registers nests anywhere near it — the deepest is a few objects inside a few arrays — so it is a ceiling on shapes nothing legitimate produces rather than a budget a caller could plausibly spend.

View Source
const DestinationRefusedMessage = "this server refused to connect to that address, so the request never left the process"

DestinationRefusedMessage is what a caller is told when this server declined to open the connection an action needed.

It says the request never left the process, because that is the fact every other reading of the symptom gets wrong: nothing was sent, nothing answered, and no credential was disclosed to the address named in the cause. Retrying changes nothing, which is why the sentence does not suggest it and the hint names a flag instead.

View Source
const ErrMsgContextCanceled = "context canceled"

ErrMsgContextCanceled is the operation label passed to WrapErr when a tool handler detects context cancellation before calling the GitLab API.

View Source
const HintPreserveLinks = "" /* 127-byte string literal not displayed */

HintPreserveLinks reminds the LLM to keep the clickable [text](url) markdown links when presenting list results to the user.

View Source
const MetaSchemaIndexURI = "gitlab://schema/meta/"

MetaSchemaIndexURI is the static URI returning the full meta-tool action catalog.

View Source
const MetaSchemaTemplateURI = "gitlab://schema/meta/{tool}/{action}"

MetaSchemaTemplateURI is the URI template for per-action params schemas.

View Source
const (
	RateLimitRefusalPrefix = "rate limit exceeded for "
)

RateLimitRefusalPrefix and rateLimitRetrySuffix are how every refusal this limiter writes begins and ends, whichever of the two wire shapes carries it.

The prefix is exported because a refused tools/call arrives as a successful JSON-RPC result whose only distinguishing mark is this text: there is no code and no _meta on that shape, so anything that has to tell a refusal from a handler failure matches the wording. cmd/bench_resources' fairness scenario does exactly that, and reading the constant rather than a copy of the sentence makes a change of wording a compile-visible edit here instead of a silent reclassification of every refusal as a failure there.

View Source
const RedactedSecretValue = "REDACTED"

RedactedSecretValue is the standard placeholder for sensitive values that must not be exposed through structured output or Markdown renderers.

View Source
const SafeModeHint = "Set GITLAB_MCP_SAFE_MODE=false to execute this operation"

SafeModeHint is the operator-facing hint attached to every safe-mode preview.

View Source
const StepFormattingResponse = "Formatting response..."

StepFormattingResponse is a standard progress step label for response formatting.

View Source
const TblFieldValue = "| Field | Value |\n| --- | --- |\n"

TblFieldValue is the standard "| Field | Value |" detail table header+separator.

View Source
const UnattributedRequestMessage = "this request could not be attributed to a credential and was not sent to GitLab; " +
	"retry, and report it if it persists"

UnattributedRequestMessage is what a caller is told when this server could not decide which credential a request belongs to.

It says what happened and asks for a report, because nothing the caller sent is wrong and nothing they can change will help: on a server shared by a configuration shape every handler resolves the caller's client from the request, and a handler that resolved none has run without one. The wording matches the refusal the subscription path already gives for the same cause, so an operator meeting both sees one fact rather than two symptoms.

What it deliberately does not say is "not found" or "your token lacks access". Both were what a caller used to see, and both send someone to check permissions that are perfectly fine.

Variables

View Source
var (
	ReadAnnotations = &mcp.ToolAnnotations{
		ReadOnlyHint:    true,
		DestructiveHint: new(false),
		IdempotentHint:  true,
		OpenWorldHint:   new(true),
	}
	CreateAnnotations = &mcp.ToolAnnotations{
		DestructiveHint: new(false),
		OpenWorldHint:   new(true),
	}
	UpdateAnnotations = &mcp.ToolAnnotations{
		DestructiveHint: new(false),
		IdempotentHint:  true,
		OpenWorldHint:   new(true),
	}
	DeleteAnnotations = &mcp.ToolAnnotations{
		DestructiveHint: new(true),
		IdempotentHint:  true,
		OpenWorldHint:   new(true),
	}
	// NonDestructiveMetaAnnotations are for meta-tools that include
	// create/update operations but no delete actions.
	NonDestructiveMetaAnnotations = &mcp.ToolAnnotations{
		DestructiveHint: new(false),
		OpenWorldHint:   new(true),
	}
	// MetaAnnotations are annotations for meta-tools that combine read/write/delete.
	// Since a single meta-tool may include destructive actions, annotations reflect
	// the most cautious combination.
	MetaAnnotations = &mcp.ToolAnnotations{
		DestructiveHint: new(true),
		OpenWorldHint:   new(true),
	}
	// ReadOnlyMetaAnnotations are for meta-tools with only list/get/search actions.
	ReadOnlyMetaAnnotations = &mcp.ToolAnnotations{
		ReadOnlyHint:    true,
		DestructiveHint: new(false),
		IdempotentHint:  true,
		OpenWorldHint:   new(true),
	}
)

Tool annotation presets for different operation categories. Each preset configures MCP hints that help LLMs understand whether a tool is read-only, destructive, or idempotent.

View Source
var (
	// ContentBoth marks content for both user display and LLM processing (default).
	ContentBoth = &mcp.Annotations{
		Audience: []mcp.Role{"user", "assistant"},
		Priority: 0.5,
	}
	// ContentUser marks content primarily for user display (uploads, visualizations).
	ContentUser = &mcp.Annotations{
		Audience: []mcp.Role{"user"},
		Priority: 0.8,
	}
	// ContentAssistant marks content primarily for LLM processing (search, raw data).
	ContentAssistant = &mcp.Annotations{
		Audience: []mcp.Role{"assistant"},
		Priority: 0.7,
	}
)

Content annotation presets for TextContent responses. These guide MCP clients on who the content is intended for and its importance.

View Source
var (
	// ContentList marks list/search results for LLM processing with lower priority.
	ContentList = &mcp.Annotations{
		Audience: []mcp.Role{"assistant"},
		Priority: 0.4,
	}
	// ContentDetail marks single-entity details for LLM processing with medium priority.
	ContentDetail = &mcp.Annotations{
		Audience: []mcp.Role{"assistant"},
		Priority: 0.6,
	}
	// ContentMutate marks create/update/delete results for LLM processing with high priority.
	ContentMutate = &mcp.Annotations{
		Audience: []mcp.Role{"assistant"},
		Priority: 0.8,
	}
)

Operation-based content annotation presets. All use audience ["assistant"] so the Markdown content is available to the LLM for reasoning, while StructuredContent (JSON) serves programmatic clients. This avoids redundant display when clients show both Content and StructuredContent.

View Source
var (
	// ResourceList marks list-shaped resources, readable by user and model.
	ResourceList = &mcp.Annotations{
		Audience: []mcp.Role{"user", "assistant"},
		Priority: 0.4,
	}
	// ResourceDetail marks single-entity resources, readable by user and model.
	ResourceDetail = &mcp.Annotations{
		Audience: []mcp.Role{"user", "assistant"},
		Priority: 0.6,
	}

	// ResourceMachineList and ResourceMachineDetail are for machine-facing
	// resources — call shapes, entry IDs, input schemas. Their intended
	// customer is the assistant deciding what to call (and programmatic
	// clients); the raw JSON has near-zero display value for a person, so
	// this is the one resource family that drops the "user" role. Every
	// other resource keeps both roles per the rationale above — and keeping
	// the split real is what makes the audience field carry information at
	// all: a value uniform across every resource tells a consumer nothing.
	ResourceMachineList = &mcp.Annotations{
		Audience: []mcp.Role{"assistant"},
		Priority: 0.4,
	}

	ResourceMachineDetail = &mcp.Annotations{
		Audience: []mcp.Role{"assistant"},
		Priority: 0.6,
	}
)

Resource-facing annotation presets.

These mirror the priorities of the operation presets above but add the "user" role, and the difference is deliberate. The assistant-only audience exists to stop a client rendering a tool result twice, once from Content and once from StructuredContent. A resource has no such duality: it is content a person asks for by URI — a workflow guide, a project's issues — so telling the client not to show it to that person describes it wrongly.

View Source
var (
	IconBranch        = icon("branch", svgBranch)
	IconCommit        = icon("commit", svgCommit)
	IconIssue         = icon("issue", svgIssue)
	IconMR            = icon("mr", svgMR)
	IconPipeline      = icon("pipeline", svgPipeline)
	IconJob           = icon("job", svgJob)
	IconRelease       = icon("release", svgRelease)
	IconTag           = icon("tag", svgTag)
	IconProject       = icon("project", svgProject)
	IconGroup         = icon("group", svgGroup)
	IconUser          = icon("user", svgUser)
	IconWiki          = icon("wiki", svgWiki)
	IconFile          = icon("file", svgFile)
	IconPackage       = icon("package", svgPackage)
	IconSearch        = icon("search", svgSearch)
	IconLabel         = icon("label", svgLabel)
	IconMilestone     = icon("milestone", svgMilestone)
	IconEnvironment   = icon("environment", svgEnvironment)
	IconDeploy        = icon("deploy", svgDeploy)
	IconSchedule      = icon("schedule", svgSchedule)
	IconVariable      = icon("variable", svgVariable)
	IconRunner        = icon("runner", svgRunner)
	IconTodo          = icon("todo", svgTodo)
	IconHealth        = icon("health", svgHealth)
	IconUpload        = icon("upload", svgUpload)
	IconBoard         = icon("board", svgBoard)
	IconSnippet       = icon("snippet", svgSnippet)
	IconToken         = icon("token", svgToken)
	IconIntegration   = icon("integration", svgIntegration)
	IconNotify        = icon("notify", svgNotify)
	IconServer        = icon("server", svgServer)
	IconSecurity      = icon("security", svgSecurity)
	IconConfig        = icon("config", svgConfig)
	IconAnalytics     = icon("analytics", svgAnalytics)
	IconKey           = icon("key", svgKey)
	IconLink          = icon("link", svgLink)
	IconDiscussion    = icon("discussion", svgDiscussion)
	IconEvent         = icon("event", svgEvent)
	IconContainer     = icon("container", svgContainer)
	IconImport        = icon("import", svgImport)
	IconAlert         = icon("alert", svgAlert)
	IconTemplate      = icon("template", svgTemplate)
	IconInfra         = icon("infra", svgInfra)
	IconEpic          = icon("epic", svgEpic)
	IconShield        = icon("shield", svgShield)
	IconAudit         = icon("audit", svgAudit)
	IconQueue         = icon("queue", svgQueue)
	IconBot           = icon("bot", svgBot)
	IconVulnerability = icon("vulnerability", svgVulnerability)
	IconCompliance    = icon("compliance", svgCompliance)
	IconBrand         = icon("brand", svgBrand)
)

Domain icons — each returns a three-element []mcp.Icon (SVG + light/dark WebP fallback) ready for the Icons field.

View Source
var ErrInvalidRateLimit = errors.New("invalid rate limit configuration")

ErrInvalidRateLimit is returned by ValidateRateLimit when the parameters are inconsistent (e.g. burst < 1 with rps > 0).

View Source
var ReadOnlyNameSuffixes = []string{
	"_list", "_lists", "_get", "_search",
	"_latest", "_blame", "_raw", "_diff", "_refs",
	"_statuses", "_signature", "_languages", "_statistics",
}

ReadOnlyNameSuffixes lists tool-name suffixes that imply a read-only operation. Shared between annotation derivation and surface-quality audits so both agree on what "looks read-only".

Functions

func AccessLevelDescription

func AccessLevelDescription(level gl.AccessLevelValue) string

AccessLevelDescription maps GitLab access level integers to human-readable labels.

func ActionDispatchOutputSchema

func ActionDispatchOutputSchema() map[string]any

ActionDispatchOutputSchema returns a permissive JSON Schema for tools whose exact structured result depends on the selected catalog action.

func ActionTimeout

func ActionTimeout() time.Duration

ActionTimeout reports the deadline every action runs under, 0 for none.

func AddMetaTool

func AddMetaTool(server *mcp.Server, name, desc string, routes ActionMap, icons []mcp.Icon, formatResult FormatResultFunc)

AddMetaTool registers an action-dispatched meta-tool with route-derived annotations. Use it for meta-tools that may include mutating or destructive actions; if any route is destructive, the tool receives DestructiveHint=true.

func AddReadOnlyMetaTool

func AddReadOnlyMetaTool(server *mcp.Server, name, desc string, routes ActionMap, icons []mcp.Icon, formatResult FormatResultFunc)

AddReadOnlyMetaTool registers an action-dispatched meta-tool whose actions are all read-only list/get/search-style operations.

func AdjustPagination

func AdjustPagination(p *PaginationOutput, itemCount int)

AdjustPagination corrects pagination metadata when the GitLab API does not return X-Total and X-Total-Pages headers (e.g., the Search API). It infers TotalItems and TotalPages from the actual item count received and the presence of a NextPage indicator.

func AppendResourceLink(_ *mcp.CallToolResult, _, _, _ string)

AppendResourceLink preserves the legacy resource-link hook as a no-op.

Deprecated: AppendResourceLink is intentionally a no-op. It previously emitted mcp.ResourceLink content blocks with external HTTP URLs (GitLab WebURL), but ResourceLink is reserved for MCP-registered resources (gitlab:// URIs). Clients that received an https:// ResourceLink attempted to resolve it via resources/read, triggering JSON-RPC -32002 "Resource not found" errors. External web links are already included in the Markdown text output. Callers will be removed in a future major version.

func ApplyActionMeta

func ApplyActionMeta(options *ActionSpecOptions, meta ActionMetaEntry)

ApplyActionMeta overlays the non-zero fields of meta onto options. Aliases and RelatedActions are copied defensively so callers may share a metadata table across actions without aliasing the underlying slices. It is a no-op for a zero-value entry, so a missing metadata row leaves the option defaults untouched.

func ApplyListOptions

func ApplyListOptions(opts *gl.ListOptions, page PaginationInput, keyset KeysetPaginationInput)

ApplyListOptions copies offset (PaginationInput) and keyset (KeysetPaginationInput) parameters onto a gl.ListOptions, setting only the values the caller supplied. Pass a zero KeysetPaginationInput for endpoints that only support offset pagination.

func AttachArgumentLimits

func AttachArgumentLimits(server *mcp.Server, maxDepth int)

AttachArgumentLimits registers a receiving middleware that refuses a tools/call whose arguments nest deeper than maxDepth, before the SDK decodes them.

Why this is not the HTTP body cap

The SDK hands tools/call arguments to middleware as raw bytes and unmarshals them into a map[string]any only when it applies the tool's schema. That decoder (github.com/segmentio/encoding/json, through the SDK's internal json package) has no maximum-nesting guard, where the standard library refuses past 10000, and it is quadratic in nesting depth: measured on v0.5.4, an 18 KB value nested 9000 deep costs over a second of CPU, and a 4 MiB body admits depth in the millions. The decode runs ahead of additionalProperties validation and ahead of the handler, so read-only mode, safe mode and the tool's own logic are all downstream of the burn, and the per-second rate limiter counts requests rather than cycles.

A body-size cap alone only narrows the window: 256 KiB still admits depth 128000. The bound that closes it is on the shape, and it belongs in a receiving middleware rather than in the HTTP front door so that stdio — the transport with no body cap at all — is covered by the same check.

The scan is a single linear pass over bytes the SDK has already framed, so the guard costs about a microsecond on an ordinary call. A non-positive maxDepth disables it; a nil server is a no-op.

func AttachRateLimit

func AttachRateLimit(server *mcp.Server, limiter *RateLimiter)

AttachRateLimit registers a receiving middleware that gates every method that reaches GitLab with the caller's credential, plus `completion/complete` and `tools/list`, when their buckets are empty.

`tools/call`, `resources/read`, `resources/subscribe`, `subscriptions/listen` and `prompts/get` draw on one bucket: each is a request to GitLab on the caller's behalf, and a limit that metered tool calls alone left the other doors open to the same upstream. They are refused differently because they fail differently. A refused tool call is reported as an MCP tool error result (IsError: true) rather than a JSON-RPC error, so the model receives a structured, retryable diagnostic and the agent loop can back off. A refused resource or prompt request is a JSON-RPC error carrying the code that mirrors HTTP 429, since those results have no error flag of their own. A refused completion returns an empty completion instead: the documented contract for this surface is that autocomplete is never blocked, and an error in a completion popup is worse than no suggestions.

`tools/list` draws on a third bucket, refilled a tenth as fast as the tool-call one and holding the same burst (see [catalogDivisor]), and it is metered for a reason none of the others share: it reaches no upstream, it spends the processor of a process many tenants share. On the individual surface one listing marshals about 3.2 MB and is the majority of that surface's processor time, so a client listing in a loop takes the processor its co-tenants are waiting for while the shared bucket, which counts requests to GitLab, sees nothing at all. Keeping the two apart is also what preserves the property the old exemption provided: draining the tool-call bucket never refuses a client's discovery. It is refused like the flagless methods above, with the code that mirrors HTTP 429, because ListToolsResult carries no error flag either. One token is one JSON-RPC request, so a catalog split over several pages costs one per page; the server keeps its whole catalog in one page, for a different reason recorded where PageSize is set.

Every other method (initialize, resources/list, prompts/list) bypasses the limiter: they reach no upstream and cost little to answer, and metering something cheap buys nothing and costs a concept. If limiter is nil, this function is a no-op.

func AttachRateLimitFunc

func AttachRateLimitFunc(server *mcp.Server, resolve func(context.Context) *RateLimiter)

AttachRateLimitFunc is AttachRateLimit for a server whose bucket depends on the request.

It exists because one server now answers for every credential of a configuration shape, while the limit is per credential: a bucket captured at registration would be one budget shared by every tenant, so the noisiest of them would refuse everybody else's calls. The resolver reads the bucket the request's own pool entry owns, and returning nil means this request is not limited, which is what an unbound request on a shape server and the stdio default both want.

func BoolEmoji

func BoolEmoji(v bool) string

BoolEmoji returns ✅ for true and ❌ for false.

func BoolPtr

func BoolPtr(b bool) *bool

BoolPtr returns a pointer to the given bool value.

func BuildMetaToolSchema

func BuildMetaToolSchema(routes ActionMap, mode string) map[string]any

BuildMetaToolSchema returns the input schema for a meta-tool given the chosen mode. Unknown modes silently fall back to MetaParamSchemaOpaque so that callers cannot break the tools/list payload by passing a typo.

  • opaque: legacy {action, params:any} envelope (default).
  • full: discriminated oneOf with full per-action params schemas.
  • compact: discriminated oneOf with descriptions and $defs stripped.

func BuildPipelineInputs

func BuildPipelineInputs(raw map[string]any) (gl.PipelineInputsOption, error)

BuildPipelineInputs converts a JSON-decoded inputs map into the SDK's type-safe gl.PipelineInputsOption. JSON numbers decode to float64 and JSON arrays to []any; both are normalized to the SDK's supported value types (string, float64, bool, []string). An unsupported value type returns an error.

Shared by pipeline creation and manual-job play, which take the same spec:inputs value shapes.

func BuildTargetURL

func BuildTargetURL(projectWebURL, targetType string, targetIID int64) string

BuildTargetURL constructs a GitLab web URL for a target resource. Returns "" when the project web URL is empty, the IID is zero, or the target type has no known URL segment.

Supported target types: Issue, MergeRequest, Milestone.

func CancelledResult

func CancelledResult(message string) *mcp.CallToolResult

CancelledResult returns an error tool result indicating the user canceled.

func CanonicalDownloadOutputPath

func CanonicalDownloadOutputPath(path string) (string, error)

CanonicalDownloadOutputPath resolves a caller-supplied destination for a file the server is about to write and returns it canonicalized, provided it lies under the working directory, the OS temporary directory, or a directory listed in GITLAB_MCP_ALLOWED_DOWNLOAD_DIRS. It refuses every path when the server is reached over HTTP.

The destination does not exist yet and neither may its parents, so the deepest existing ancestor is what gets resolved through symlinks; the segments below it cannot be symlinks because they do not exist. A leaf that does exist must be a regular file: a symlink there would redirect the write to whatever it names, which is how an "output path" becomes a way to overwrite an SSH key.

Call it again after creating the parent directories. The second call resolves a parent that now exists, which is what turns the check from a promise about the path into a check on the directory being written to.

An existing regular file is overwritten, deliberately, and there is no caller opt-in to refuse it. The audit that produced the symlink check asked for one, and the trade is not worth taking: an opt-in is a new field on DownloadInput, which is a served input schema, so it lands in the tool snapshots, all three llms artifacts, the token-footprint tables and the per-domain docs. What it buys is bounded by two things that are already true. The destination can only be inside the working directory, the OS temporary directory or a directory the operator allow-listed, so the file at risk is one in the workspace rather than one belonging to the system; and under stdio, which is the only transport where a caller-supplied path is honored at all, whatever is driving this server holds its own filesystem write and can overwrite that same file directly. The opt-in would be one bool plus a regeneration pass if the calculus ever changes, but the residual risk it removes is smaller than the surface it adds.

func CanonicalImportArchivePath

func CanonicalImportArchivePath(path string) (string, error)

CanonicalImportArchivePath validates a local GitLab export archive path and returns the canonical path resolved through symlinks. Archives must be regular .tar.gz files under the current working directory, the OS temporary directory, or a directory listed in GITLAB_MCP_ALLOWED_IMPORT_DIRS.

func CanonicalLocalDirPath

func CanonicalLocalDirPath(path string) (string, error)

CanonicalLocalDirPath resolves a caller-supplied path to an existing local directory and returns it canonicalized, subject to the same roots and the same HTTP refusal as CanonicalLocalFilePath.

func CanonicalLocalFilePath

func CanonicalLocalFilePath(path string) (string, error)

CanonicalLocalFilePath resolves a caller-supplied path to an existing local file and returns it canonicalized, provided the resolved path lies under the working directory, the OS temporary directory, or a directory listed in GITLAB_MCP_ALLOWED_UPLOAD_DIRS. It refuses every path when the server is reached over HTTP.

func CatalogListingRPS

func CatalogListingRPS(rps float64) float64

CatalogListingRPS is the refill rate a listing draws on when the tool-call bucket is configured at rps.

Exported so the entrypoint can announce both figures at startup: the listing rate is the other number an operator can meet in a refusal, and the divisor that produces it belongs here rather than copied into a log statement. The burst is the configured one, unchanged; [catalogDivisor] records why.

func ClampPollInterval

func ClampPollInterval(v int) int

ClampPollInterval constrains a polling interval to [PollMinInterval, PollMaxInterval], returning PollDefaultInterval when the value is below the minimum.

func ClampPollTimeout

func ClampPollTimeout(v int) int

ClampPollTimeout constrains a polling timeout to [PollMinTimeout, PollMaxTimeout], returning PollDefaultTimeout when the value is below the minimum.

func ClassifyError

func ClassifyError(err error) string

ClassifyError inspects the error chain and returns a short, human-friendly diagnostic message explaining what went wrong at a high level.

func ClassifyHTTPStatus

func ClassifyHTTPStatus(code int) string

ClassifyHTTPStatus returns a semantic description for common HTTP status codes.

func CloneMetaSchemaRoutes

func CloneMetaSchemaRoutes(routes map[string]ActionMap) map[string]ActionMap

CloneMetaSchemaRoutes returns a snapshot of the route maps: the two map levels are new, so later insertions and deletions in routes do not reach the snapshot, and every route in it is a CloneActionRoute copy.

The schemas are not copied. A route's InputSchema, OutputSchema and ParameterGuidance are frozen and shared by every consumer in the process, which is what lets a catalog cached per configuration serve every server without a copy per server; the copies this function used to make were half of the heap at a hundred pooled credentials. A consumer that must change a schema derives its own through DeriveSchema.

func CloneSchemaMap

func CloneSchemaMap(schema map[string]any) map[string]any

CloneSchemaMap returns a deep copy of a JSON Schema map, for a caller that has a reason to mutate one: every schema reachable from a shared catalog is shared with every server in the process and must be copied before it is changed.

func CompileToolSchemas

func CompileToolSchemas(tool *mcp.Tool, cacheKey string)

CompileToolSchemas replaces a projected tool's map-based input and output schemas with process-cached *jsonschema.Schema equivalents. Passing the SDK a stable schema pointer instead of a map removes the per-registration JSON remarshal inside mcp.AddTool and lets a shared mcp.SchemaCache (keyed by pointer identity) skip schema resolution on subsequent registrations — the dominant cost when the HTTP server pool builds a new MCP server per token.

The conversion is a faithful roundtrip: the maps are produced by marshaling jsonschema output, and jsonschema.Schema preserves non-standard keys such as x_destructive. On any marshal error the original map is kept, preserving current behavior. An empty cacheKey disables compilation, so callers that cannot guarantee a content-stable key keep the map path.

func ComputeSHA256

func ComputeSHA256(path string) (string, error)

ComputeSHA256 computes the SHA-256 checksum of a file at the given path and returns the lowercase hex-encoded hash string.

func ComputeSHA256Reader

func ComputeSHA256Reader(r io.Reader) (string, error)

ComputeSHA256Reader computes the SHA-256 checksum from an arbitrary io.Reader and returns the lowercase hex-encoded hash string.

func ConfirmAction

func ConfirmAction(ctx context.Context, req *mcp.CallToolRequest, message string) (*mcp.CallToolResult, error)

ConfirmAction uses MCP elicitation to ask the user for confirmation before a destructive action. Returns nil if the user confirmed or elicitation is unsupported (fallback: action proceeds — destructive callers must go through ConfirmDestructiveAction, which fails closed in that case). Returns an error tool result if the user declined or canceled. On sessions negotiated at protocol >= 2026-07-28 the confirmation travels as a multi round-trip input request (SEP-2322): the first pass returns an input-required result and the client retries the call with the user's answer attached.

func ConfirmDestructiveAction

func ConfirmDestructiveAction(ctx context.Context, req *mcp.CallToolRequest, params map[string]any, message string) (*mcp.CallToolResult, error)

ConfirmDestructiveAction checks whether a destructive action should proceed. The confirmation flow is:

  1. GITLAB_MCP_YOLO_MODE / AUTOPILOT env var → skip confirmation entirely
  2. Explicit "confirm": true in params → skip confirmation
  3. MCP elicitation supported → ask user interactively
  4. Elicitation unsupported and no confirm param → fail closed: return an error result prompting the caller to re-send with confirm: true

Returns nil, nil if the action should proceed. Returns a non-nil *mcp.CallToolResult if the action was canceled or requires explicit confirmation.

The error return is for protocol faults rather than tool outcomes: a requestState this server did not issue, one carrying a version it does not know, or an inputResponses value of the wrong type. "Protocol errors (malformed JSON, invalid schema, internal server errors) SHOULD return a JSON-RPC error response with an appropriate error code and message", and none of those three is something the model wrote or can correct — putting them in a tool result asks it to fix a field its client mangled. Everything a caller can act on (declined, cancelled, a client that cannot prompt) stays in the result, which is where MCP wants tool-level failure.

func ContainsAny

func ContainsAny(err error, substrs ...string) bool

ContainsAny returns true if err.Error() contains any of the substrings.

func ContextWithRequest

func ContextWithRequest(ctx context.Context, req *mcp.CallToolRequest) context.Context

ContextWithRequest returns a derived context carrying the MCP request.

func CopyReadOnlyMetaAnnotations

func CopyReadOnlyMetaAnnotations() *mcp.ToolAnnotations

CopyReadOnlyMetaAnnotations returns a copy of ReadOnlyMetaAnnotations so callers never alias the shared singleton.

Note on ToolAnnotations.Title: it is deliberately left unset here and in DeriveAnnotations. Since MCP 2025-06-18 a Tool carries its own top-level Title, and the display-name precedence is Title → Annotations.Title → Name, so setting both only duplicates the string in every tools/list response. The individual-tool projection has always relied on the top-level Title alone; the meta surface now matches it.

func CreateDownloadOutputFile

func CreateDownloadOutputFile(path string) (*os.File, error)

CreateDownloadOutputFile creates the destination a download writes to, refusing a symlink at the leaf where the platform can.

CanonicalDownloadOutputPath refuses a destination that is already a symlink, but it refuses a path, and the file is created by a later syscall: a local principal who can write in an allowed root can put a symlink there in between and redirect the write to whatever the server may overwrite. The creation is the only place that race can be closed, so it happens here rather than at the call site.

func DefuseHintsHeading

func DefuseHintsHeading(s string) string

DefuseHintsHeading rewrites every copy of the server's guidance heading in s so it can no longer pass for one. Text with no such heading is returned unchanged.

func DeriveAnnotations

func DeriveAnnotations(routes ActionMap) *mcp.ToolAnnotations

DeriveAnnotations computes tool-level MCP annotations from the route map. If any route is destructive, returns a copy of MetaAnnotations (DestructiveHint: true). If all routes are non-destructive, returns a copy of NonDestructiveMetaAnnotations. Each call returns a fresh copy to avoid aliasing the shared singletons.

func DeriveSchema

func DeriveSchema(schema any, transform string, derive func() any) any

DeriveSchema returns transform applied to schema, where derive builds the result and transform names the rewrite it performs. The name must identify the rewrite completely: two calls with the same name on the same schema are assumed to want the same output, so any parameter the rewrite depends on belongs in the name.

For a schema registered with ShareSchema the result is built once per process, registered as shared in turn, and returned to every later caller; derive must therefore never mutate its input. For any other schema the result is built privately on every call, exactly as before this memo existed. A transform reapplied to its own output returns that output.

func DetectRichContent

func DetectRichContent(body string) string

DetectRichContent scans a GFM body for non-portable features that may not render correctly outside GitLab (mermaid diagrams, math blocks, raw HTML). Returns a comma-separated list of detected features or an empty string.

func EmbedCanonicalResource

func EmbedCanonicalResource(result *mcp.CallToolResult, template string, params map[string]any, value any)

EmbedCanonicalResource appends the canonical resource of a successful action result: the template is the one the action's spec declares, expanded from the parameters the call carried, and the payload is the JSON form of the output. It is a no-op without a template, on an error result, when a variable is missing, or when embedding is disabled, so every dispatcher can call it unconditionally after formatting.

func EmbedResource

func EmbedResource(result *mcp.CallToolResult, uri, mimeType, text string)

EmbedResource appends an EmbeddedResource content block to result that references the canonical MCP resource URI for the entity returned by the tool. mimeType should typically be "application/json" with text containing a compact JSON serialization of the entity (or empty if the resource is addressable but the body is large).

When result is nil, the URI is empty, or the global toggle is disabled, EmbedResource is a no-op. No further URI validation is performed; callers are responsible for passing well-formed URIs that match an MCP resource template registered with the server.

func EmbedResourceJSON

func EmbedResourceJSON(result *mcp.CallToolResult, uri string, value any)

EmbedResourceJSON marshals value as JSON and embeds the result with MIME type application/json. Marshaling errors are dropped silently — the tool result is still returned with text and StructuredContent so the LLM has a usable response.

func EmbeddedResourcesEnabled

func EmbeddedResourcesEnabled() bool

EmbeddedResourcesEnabled reports the current state of the global toggle. Exposed for tests; production code should call EmbedResource directly.

func EnableEmbeddedResources

func EnableEmbeddedResources(enabled bool)

EnableEmbeddedResources toggles the global EmbedResource behavior. When false, EmbedResource is a no-op, preserving the legacy two-block (text + structuredContent) tool result shape.

func EnrichPaginationConstraints

func EnrichPaginationConstraints(server *mcp.Server)

EnrichPaginationConstraints registers a receiving middleware that walks every tools/list response and injects JSON Schema numeric constraints on the standard pagination property names so LLM clients see the bounds directly in tools/list rather than only through prose in the description.

The middleware operates per property name:

  • `page` gets `minimum: 1`
  • `per_page` gets `minimum: 1` and `maximum: 100`

Existing constraints are preserved: if a schema already declares `minimum` or `maximum` on these properties the middleware leaves them untouched so domain-specific overrides remain authoritative. Only nodes whose `type` is `integer` or `number` (or unset, defaulting to integer per the Go SDK's int-typed pagination input) are modified, so unrelated properties named `page` on a custom schema cannot be silently mutated.

The transformation runs after LockdownInputSchemas so it sees the same fully-populated schema set every list/tools response carries.

Concurrency. As with LockdownInputSchemas, the mutation runs through [onFirstToolsList], whose `sync.Once` guard prevents concurrent `tools/list` calls from racing on the shared *Tool.InputSchema maps.

Sharing. Like the lockdown, this derives the enriched schema instead of changing the one in place (see DeriveSchema): the lockdown's output is shared across servers, and so is this one.

func ErrFieldRequired

func ErrFieldRequired(field string) error

ErrFieldRequired returns a validation error indicating that a required field is missing or empty. It produces the message "<field> is required", which is the standard validation pattern used across all tool handlers.

func ErrInvalidEnum

func ErrInvalidEnum(field, value string, validValues []string) error

ErrInvalidEnum returns a validation error indicating that a field value is not one of the allowed options. The error message lists the valid values to guide LLMs toward correct parameter usage.

func ErrRequiredInt64

func ErrRequiredInt64(operation, field string) error

ErrRequiredInt64 returns a formatted error when a required int64 field is missing or has its zero value. This catches silent deserialization failures in meta-tool dispatch, where a misnamed JSON parameter (e.g. "mr_iid" instead of "merge_request_iid") is silently ignored and the field defaults to 0.

func ErrRequiredString

func ErrRequiredString(operation, field string) error

ErrRequiredString returns a formatted error when a required string field is missing or empty. Like ErrRequiredInt64, this guides LLMs to use the exact parameter name when silent deserialization failures occur.

func ErrorResult

func ErrorResult(message string) *mcp.CallToolResult

ErrorResult builds a standard error mcp.CallToolResult with IsError set. It returns the error message as Markdown content for display.

func ErrorResultMarkdown

func ErrorResultMarkdown(domain, action string, err error) *mcp.CallToolResult

ErrorResultMarkdown creates an MCP tool error result with Markdown formatting. The result has IsError = true for MCP clients that distinguish error results.

func EscapeConsentValue

func EscapeConsentValue(s string) string

EscapeConsentValue renders caller-controlled text for a consent dialog.

"MCP servers requesting elicitation SHOULD NOT include URLs intended to be clickable in any field of a form mode elicitation request." The dialog is where a person decides whether to allow an action, so what it shows has to be the server's own words plus data that cannot pass for them. Interpolating raw values did not meet that: a project path of

demo/proj\n\n**SECURITY NOTICE** Verify at https://evil.example/verify

reached the user as bold text and a link inside the question they were being asked, all of it attacker-supplied by way of the model.

Three things happen here. Line breaks collapse, so a value cannot add paragraphs to the dialog's structure. URL schemes are defanged to https[:]//, which stays legible while leaving nothing for a client to linkify. And the result is fenced in backticks, so emphasis, headings and link syntax inside it are shown rather than rendered — using a fence longer than any run of backticks in the value, the same rule Markdown itself uses for nesting code.

This is escaping, not sanitizing: the text still reaches the user, and should, since a project path they cannot read is no use to them deciding. What it can no longer do is impersonate the server asking the question.

func EscapeMdHeading

func EscapeMdHeading(s string) string

EscapeMdHeading sanitizes a user-controlled string that will be interpolated into a Markdown heading (e.g. `## Project: {name}`). It strips leading '#' characters that could promote/demote the heading level and collapses newlines into spaces so the heading stays on one line.

The opening angle bracket is escaped for the reason EscapeMdTableCell gives, and in the same entity form: a heading is the other place a formatter puts a GitLab-authored name, all 44 call sites interpolate one value into a heading the server wrote, and the same name should not turn into a live tag in a heading after being neutralized in a cell.

func EscapeMdLinkDestination

func EscapeMdLinkDestination(url string) string

EscapeMdLinkDestination renders url as the destination of a Markdown link.

func EscapeMdLinkLabel

func EscapeMdLinkLabel(s string) string

EscapeMdLinkLabel renders s as the visible text of a Markdown link.

A label is not a cell: escaping the pipe keeps the row intact and does nothing about the bracket. An issue titled

Fix login](http://attacker.invalid/x)

closed the label after "Fix login" and opened a destination of its own, so a reader saw the issue's title linking to a host that is not GitLab — on this server's own instruction, since HintPreserveLinks tells the model to keep the clickable links so the user can navigate to GitLab.

func EscapeMdTableCell

func EscapeMdTableCell(s string) string

EscapeMdTableCell escapes characters in s that would break a Markdown table row. Pipes are replaced with the HTML entity &#124; and newlines/carriage-returns are replaced with a space so the cell stays on a single row. Control characters are dropped by StripControlBytes.

The opening angle bracket is an entity too, for the reason [mdLinkLabelEscaper] gives: a renderer takes raw HTML ahead of whatever surrounds it, so a title of

<a href="http://attacker.invalid/x">Fix login</a>

arrives as a working link to a host that is not GitLab. MdTitleLink closed that for the link case and returns this escaper's output untouched when the item has no URL, which is the ordinary shape for something with no page of its own, and around 266 formatters call this one directly.

An entity rather than the backslash the label escaper uses, because MdTitleLink pipes a cell through that escaper: a backslash arriving there is escaped again into a visible one, where &lt; passes through untouched and renders as the character it always was.

Only '<' is escaped. Everything a renderer obeys instead of reading opens with it (a tag, a comment, an autolink), so that is the whole of the containment, and '>' is left alone because callers compose cells of their own: a merge request's branch cell reads "feature/fix -> develop", and entity-encoding the arrow the server itself wrote damages output that no untrusted text ever touched.

func ExceedsJSONDepth

func ExceedsJSONDepth(raw []byte, limit int) bool

ExceedsJSONDepth reports whether raw nests containers deeper than limit.

Convenience wrapper over JSONDepthScanner for a value that is already whole in memory.

func ExecGraphQLDestroyNote

func ExecGraphQLDestroyNote(ctx context.Context, gql gl.GraphQLInterface, op, hint, query, noteGID string) error

ExecGraphQLDestroyNote runs the destroyNote work item mutation, applying the shared error conventions: transport errors are wrapped with op + hint and the first mutation payload error becomes "op: message".

func ExecGraphQLNoteMutation

func ExecGraphQLNoteMutation[N any](ctx context.Context, gql gl.GraphQLInterface, m GraphQLNoteMutation) (*N, error)

ExecGraphQLNoteMutation runs a work item note mutation and returns the mutated note node decoded as N, applying the shared error conventions described on GraphQLNoteMutation.

func ExpandResourceURI

func ExpandResourceURI(template string, params map[string]any) (string, bool)

ExpandResourceURI fills a canonical resource URI template from an action's parameters. Each {name} is replaced by the path-escaped value of the parameter of that name, so a project given as "group/project" lands in the URI as group%2Fproject, which is the form the resource templates accept; {+name} keeps the slashes of a path-valued parameter, escaping each segment. It reports false when the template is empty or any variable is absent or empty, so a result whose identifier the caller never supplied gets no resource block instead of a URI with a hole in it.

func ExplicitConfirmFromRequest

func ExplicitConfirmFromRequest(req *mcp.CallToolRequest) bool

ExplicitConfirmFromRequest reports whether the caller set the reserved confirm key on this tool call.

The key never reaches a typed action input: [stripReservedKeys] removes it before strict unmarshalling so it cannot trip the unknown-field rejection. A handler that needs to know whether the caller confirmed — because its destructiveness depends on runtime state and cannot be declared on the route — must therefore read it from the raw arguments, which this does for both call shapes: flat on individual tools, and nested under params on the dispatcher surfaces.

func ExtractGitLabMessage

func ExtractGitLabMessage(err error) string

ExtractGitLabMessage extracts the specific error message from a GitLab ErrorResponse in the error chain. Returns empty string if not found, if the message only repeats the HTTP status text (e.g. "405 Method Not Allowed"), or if it is an unparsed upstream response body rather than a GitLab message.

What survives is flattened onto one line and truncated to 300 characters. GitLab's messages routinely quote input an attacker chose — a branch name, a path, a title — so the span has to stay a span: with its newlines intact it could add structure to the error text a model reads.

func ExtractHints

func ExtractHints(md string) []string

ExtractHints parses the "💡 Next steps" section from a Markdown tool response and returns the individual hint strings. Returns nil when the section is absent.

Only a section in a position the server could have written it is read, and there are exactly two: WriteHints is called either on an empty builder, so the section opens the response (nineteen list formatters do this), or after the body, so it closes it. A section anywhere in between is content that happens to look like guidance — a README, a job log, a project description — and content does not get to speak as the server. This used to be the first match anywhere in the response, which is what let a file's contents fill next_steps.

func FieldTiers

func FieldTiers(rt reflect.Type) map[string]string

FieldTiers returns a map from top-level JSON field name to the minimum licensing tier declared via the `tier:"premium"` / `tier:"ultimate"` struct tag on the input/output type rt. Fields without a tier tag (the common case, Free) are omitted. Anonymous embedded structs are flattened, mirroring the JSON field promotion used by [secretJSONFieldNames]. The result drives per-instance-tier schema pruning so Free/Premium instances never see fields that require a higher tier.

func FormatCICDVariableCollectionMarkdown

func FormatCICDVariableCollectionMarkdown[T any](variables []T, pagination PaginationOutput, convert func(T) CICDVariableMarkdown, title, emptyMessage string, includeEnvironmentScope bool, hints ...string) string

FormatCICDVariableCollectionMarkdown maps package-specific CI/CD variable outputs and renders them as a shared Markdown list.

func FormatCICDVariableDetailMarkdown

func FormatCICDVariableDetailMarkdown(v CICDVariableMarkdown, title string, includeEnvironmentScope bool) string

FormatCICDVariableDetailMarkdown renders a CI/CD variable with standard update/delete next-step hints shared by project and group variables.

func FormatCICDVariableListMarkdown

func FormatCICDVariableListMarkdown(variables []CICDVariableMarkdown, pagination PaginationOutput, opts CICDVariableListMarkdownOptions) string

FormatCICDVariableListMarkdown renders CI/CD variables as a Markdown table.

func FormatCICDVariableMarkdown

func FormatCICDVariableMarkdown(v CICDVariableMarkdown, opts CICDVariableMarkdownOptions) string

FormatCICDVariableMarkdown renders a single CI/CD variable as a Markdown detail table.

func FormatDiscussionListMarkdown

func FormatDiscussionListMarkdown(discussions []DiscussionMarkdown, opts DiscussionListMarkdownOptions) string

FormatDiscussionListMarkdown renders discussion threads as Markdown.

func FormatDiscussionMarkdown

func FormatDiscussionMarkdown(discussion DiscussionMarkdown, hints ...string) string

FormatDiscussionMarkdown renders a single discussion thread as Markdown.

func FormatDiscussionNoteMarkdown

func FormatDiscussionNoteMarkdown(note DiscussionNoteMarkdown, hints ...string) string

FormatDiscussionNoteMarkdown renders a single discussion note as Markdown.

func FormatGID

func FormatGID(typeName string, id int64) string

FormatGID builds a GitLab Global ID string from a type name and numeric ID.

FormatGID("Vulnerability", 42) → "gid://gitlab/Vulnerability/42"

func FormatGraphQLDiscussionListMarkdown

func FormatGraphQLDiscussionListMarkdown[T any](discussions []T, pagination GraphQLPaginationOutput, convert func(T) DiscussionMarkdown, title, emptyMessage string, hints ...string) string

FormatGraphQLDiscussionListMarkdown maps GraphQL discussion outputs and renders them with cursor pagination metadata.

func FormatGraphQLForwardPagination

func FormatGraphQLForwardPagination(p GraphQLForwardPaginationOutput, shown int) string

FormatGraphQLForwardPagination renders the pagination metadata of a forward-only connection, which never names a previous page.

func FormatGraphQLPagination

func FormatGraphQLPagination(p GraphQLPaginationOutput, shown int) string

FormatGraphQLPagination renders cursor-based pagination metadata as a Markdown summary line, suitable for appending to list tool responses.

func FormatISOTimePtr

func FormatISOTimePtr(t *gl.ISOTime) string

FormatISOTimePtr renders an optional *gl.ISOTime as YYYY-MM-DD, or "" when nil.

func FormatLabelListMarkdown

func FormatLabelListMarkdown(labels []LabelMarkdown, pagination PaginationOutput, opts LabelMarkdownOptions) string

FormatLabelListMarkdown renders project or group labels as a paginated table.

func FormatLabelListMarkdownFunc

func FormatLabelListMarkdownFunc[T any](labels []T, pagination PaginationOutput, opts LabelMarkdownOptions, convert func(T) LabelMarkdown) string

FormatLabelListMarkdownFunc renders labels after mapping domain-specific outputs to the shared Markdown view.

func FormatLabelMarkdown

func FormatLabelMarkdown(label LabelMarkdown, opts LabelMarkdownOptions) string

FormatLabelMarkdown renders a project or group label as a Markdown summary.

func FormatNoteListMarkdown

func FormatNoteListMarkdown(notes []NoteMarkdown, pagination PaginationOutput, opts NoteListMarkdownOptions) string

FormatNoteListMarkdown renders a list of GitLab notes as Markdown.

func FormatNoteMarkdown

func FormatNoteMarkdown(note NoteMarkdown, opts NoteMarkdownOptions) string

FormatNoteMarkdown renders a single GitLab note as Markdown.

func FormatPagination

func FormatPagination(p PaginationOutput) string

FormatPagination renders pagination metadata as a compact Markdown line.

func FormatRESTDiscussionListMarkdown

func FormatRESTDiscussionListMarkdown[T any](discussions []T, pagination PaginationOutput, convert func(T) DiscussionMarkdown, title, emptyMessage string, hints ...string) string

FormatRESTDiscussionListMarkdown maps REST discussion outputs and renders them with offset pagination metadata.

func FormatStorageMoveCollectionMarkdown

func FormatStorageMoveCollectionMarkdown[T any](moves []T, pagination PaginationOutput, convert func(T) StorageMoveMarkdown, title, emptyMessage, entityColumn string) string

FormatStorageMoveCollectionMarkdown maps package-specific storage moves and renders them as a shared Markdown list.

func FormatStorageMoveDetailMarkdown

func FormatStorageMoveDetailMarkdown(move StorageMoveMarkdown, title string, hints ...string) string

FormatStorageMoveDetailMarkdown renders one repository storage move as a Markdown detail table.

func FormatStorageMoveListMarkdown

func FormatStorageMoveListMarkdown(moves []StorageMoveMarkdown, opts StorageMoveListMarkdownOptions) string

FormatStorageMoveListMarkdown renders repository storage moves as a Markdown table with the domain-specific entity column supplied by the caller.

func FormatTarget

func FormatTarget(targetType string, targetIID int64, targetTitle, targetURL string) string

FormatTarget builds a Markdown table cell for a typed target resource. When targetURL is non-empty, the result is a clickable link like [Issue #42](url). When empty, the label is returned as plain text. Returns "" if there is nothing to display.

func FormatTemplateAttributeListMarkdown

func FormatTemplateAttributeListMarkdown(items []TemplateAttributeListMarkdownItem, opts TemplateAttributeListMarkdownOptions) string

FormatTemplateAttributeListMarkdown renders a common Key/Name/Attribute template list without changing the JSON schema used by existing template tools.

func FormatTemplateCollectionMarkdown

func FormatTemplateCollectionMarkdown[T any](templates []T, pagination PaginationOutput, convert func(T) TemplateMarkdown, title, emptyMessage string, hints ...string) string

FormatTemplateCollectionMarkdown maps package-specific template outputs and renders them as a shared Markdown list.

func FormatTemplateContentMarkdown

func FormatTemplateContentMarkdown(title, name, language, content string, hints ...string) string

FormatTemplateContentMarkdown renders a GitLab template body inside a fenced code block.

func FormatTemplateDetailMarkdown

func FormatTemplateDetailMarkdown(detail TemplateDetailMarkdown) string

FormatTemplateDetailMarkdown renders a shared template detail layout.

func FormatTemplateListMarkdown

func FormatTemplateListMarkdown(templates []TemplateMarkdown, pagination PaginationOutput, opts TemplateListMarkdownOptions) string

FormatTemplateListMarkdown renders GitLab template list entries as Markdown.

func FormatTime

func FormatTime(s string) string

FormatTime converts an RFC3339 timestamp string to a human-readable format ("2 Jan 2006 15:04 UTC"), falling back to the date-only layout and then to the string it was given.

Every one of this function's callers writes the result straight into Markdown, so the fallback runs it through EscapeMdTableCell rather than returning it as it arrived. A value that reaches the fallback is by definition not a timestamp, which leaves only two ways to get there: a field this server formatted itself and a field carrying whatever GitLab put in it. Returning the second verbatim put a pipe, a newline and a '<' into a table cell from 155 call sites, and telling each of those call sites to escape a timestamp would have taught the next reader that a date needs escaping. Escaping here costs nothing that renders: neither layout's output contains any character the escaper touches.

func FormatTimePtr

func FormatTimePtr(t *time.Time) string

FormatTimePtr renders an optional *time.Time as RFC 3339, or "" when nil.

func GraphQLMutationError

func GraphQLMutationError(operation string, payloadErrors []string) error

GraphQLMutationError formats mutation payload errors, if any.

func GraphQLTopLevelError

func GraphQLTopLevelError(operation string, responseErrors []GraphQLError) error

GraphQLTopLevelError formats top-level GraphQL response errors, if any.

func HasRegisteredMarkdownFormatter

func HasRegisteredMarkdownFormatter(v any) bool

HasRegisteredMarkdownFormatter reports whether a Markdown formatter has been registered for the given Go type. Accepts either a reflect.Type (the canonical lookup path — matches spec.Route.OutputType) or a value of any kind (used by tests). Pointer types are dereferenced to their element type for the registry lookup. Returns false for nil, for the special "interface" reflect.Type returned by reflect.TypeOf on a nil/untyped interface, and for unregistered types.

This is the authoritative source for "is there a Markdown formatter for this output type?" queries from external tools (e.g. cmd/audit_discovery_completeness uses it to gate the `missing_next_steps` check).

func IdentityToContext

func IdentityToContext(ctx context.Context, id UserIdentity) context.Context

IdentityToContext stores a UserIdentity in the context. Used at startup in stdio mode to make the identity available to all tool handlers.

func ImageMIMEType

func ImageMIMEType(filename string) string

ImageMIMEType returns the MIME type for image file extensions. Returns an empty string for non-image files.

func IndividualToolFromActionSpec

func IndividualToolFromActionSpec(spec ActionSpec, opts IndividualToolProjectionOptions) (*mcp.Tool, error)

IndividualToolFromActionSpec projects canonical action metadata into an MCP tool definition for the individual-tool surface.

func IndividualToolFromSpecs

func IndividualToolFromSpecs(specs []ActionSpec, individualName string, opts IndividualToolProjectionOptions) (*mcp.Tool, error)

IndividualToolFromSpecs projects the spec that owns an individual tool name.

func InputRequiredResultFromError

func InputRequiredResultFromError(err error) (*mcp.CallToolResult, bool)

InputRequiredResultFromError extracts the input-required tool result from a handler error produced by elicitation.Flow.PendingError. Surface dispatchers call this before logging so a pending multi round-trip exchange is returned to the client instead of being reported as a handler failure.

func InternalError

func InternalError(err error) error

InternalError marks an error as JSON-RPC -32603: an upstream call failed or a response would not decode, and there is nothing the caller can change.

func InvalidParams

func InvalidParams(err error) error

InvalidParams marks an error as JSON-RPC -32602: the caller sent something missing, empty or unparseable, and has to change it.

func IsBinaryFile

func IsBinaryFile(filename string) bool

IsBinaryFile returns true when the filename has a known binary extension that is not an image. Returns false for text and image files.

func IsDeleteToolName

func IsDeleteToolName(name string) bool

IsDeleteToolName reports whether name contains or ends with "delete".

func IsHTTPStatus

func IsHTTPStatus(err error, code int) bool

IsHTTPStatus reports whether err wraps a GitLab ErrorResponse with the given HTTP status code. Useful for handling specific API responses like 404 (feature not available on CE) or 403 (insufficient permissions).

func IsImageFile

func IsImageFile(filename string) bool

IsImageFile returns true when the filename has an image extension. Comparison is case-insensitive. Returns false for empty strings.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether err represents a 404 Not Found, either via a structured GitLab ErrorResponse status code or via a plain-text error message from client-go (which may contain "404 Not Found" as text).

func IsReadToolName

func IsReadToolName(name string) bool

IsReadToolName reports whether name ends with a read-only suffix.

func IsTerminalStatus

func IsTerminalStatus(status string) bool

IsTerminalStatus reports whether a CI/CD status represents a finished state.

func IsYOLOMode

func IsYOLOMode() bool

IsYOLOMode returns true when destructive action confirmation should be skipped entirely. It reads GITLAB_MCP_YOLO_MODE (or its old spelling YOLO_MODE, warned about at startup like every other renamed setting) and falls back to AUTOPILOT, a convention other agent tooling sets. Any truthy value (1, true, yes — case-insensitive) enables the mode.

func IssueStateEmoji

func IssueStateEmoji(state string) string

IssueStateEmoji returns the Markdown emoji for an issue state.

func ListHints

func ListHints(hints ...string) []string

ListHints prepends HintPreserveLinks to list-result next-step hints.

func ListRegisteredTools

func ListRegisteredTools(ctx context.Context, server *mcp.Server, clientName string) ([]*mcp.Tool, error)

ListRegisteredTools lists tools registered on a server through an ephemeral in-memory MCP client session.

func LocalFilesystemAccessAllowed

func LocalFilesystemAccessAllowed() bool

LocalFilesystemAccessAllowed reports whether caller-supplied local paths are honored in this process.

func LockdownInputSchemas

func LockdownInputSchemas(server *mcp.Server)

LockdownInputSchemas registers a receiving middleware that rewrites tools/list responses so every tool's inputSchema declares `additionalProperties: false` at the root and on any nested object schema reachable through "properties", "items", "anyOf", "oneOf", or "allOf". It also strips jsonschema tag metadata such as ",required" from property descriptions after the SDK generates schemas. Schemas converted from SDK types are stored back as map[string]any values, so callers inspecting tools after this middleware runs should not expect the original concrete schema type.

Background. The MCP specification (2025-11-25 §server/tools) requires inputSchema to be a valid JSON Schema object but does not mandate `additionalProperties`. JSON Schema 2020-12 default semantics treat an unspecified `additionalProperties` as `true`, which silently accepts unknown fields. When an LLM mistypes an argument name (e.g. "projetc_id" instead of "project_id"), the server forwards an empty value to the handler, which then fails with a confusing "missing parameter" error rather than the actionable "unknown property" diagnostic the LLM needs to self-correct.

Schemas that already declare `additionalProperties` (true or false) at a given level are left untouched, so meta-tool router branches that intentionally permit unknown fields for forward compatibility remain intact.

Concurrency. The MCP Go SDK does not expose a public API to enumerate registered tools at startup, so the transformation runs inside a `tools/list` middleware via [onFirstToolsList], which guards the mutation with a `sync.Once`.

Sharing. The schema a tool carries at that point is never changed in place: a compiled schema is shared by every server in the process through the compile cache, and a map may be shared through a catalog. The locked down form is derived from it, once per process for a shared schema (see DeriveSchema), so a thousand pooled servers list one set of maps.

func LogToolCallAll

func LogToolCallAll(ctx context.Context, req *mcp.CallToolRequest, tool string, start time.Time, result any, err error)

LogToolCallAll logs a tool call to stderr (slog). It is the standard logging function for all tool handlers. When the request contains authenticated user identity (any mode), it includes the user in the log output for audit trail purposes.

result is what the handler returned, taken so the record can say whether the call actually succeeded. A handler that reports failure in its result rather than as a Go error, which NotFoundResult does in every get handler that uses it, used to be logged as "tool call completed" with nothing distinguishing it from a call that worked. An operator could not compute an error rate from this stream, because the stream did not contain one.

func LogToolRefusal

func LogToolRefusal(ctx context.Context, req *mcp.CallToolRequest, tool, reason string)

LogToolRefusal records a call the server declined to run.

These paths returned an error result to the model and wrote nothing at all: safe-mode previews, unknown actions, missing parameters and unconfirmed destructive actions each returned before reaching LogToolCallAll. They are refusals rather than failures, so they are INFO, and they are exactly the events an operator most wants to count: a deployment refusing every third call because its clients have not learned the parameter shape looks identical to a healthy one from the logs.

It is the second half of the observability item ADR-0011 already accepted: "add observability for find query, selected action, validation failure, policy block, and destructive confirmation events". Only the destructive one was delivered.

func LookupMetaActionSchema

func LookupMetaActionSchema(routes map[string]ActionMap, tool, action string) (map[string]any, bool)

LookupMetaActionSchema returns the per-action params schema for a tool/action pair.

func MRStateEmoji

func MRStateEmoji(state string) string

MRStateEmoji returns the Markdown emoji for a merge request state.

func MakeMetaHandler

func MakeMetaHandler(toolName string, routes ActionMap, formatResult FormatResultFunc) func(ctx context.Context, req *mcp.CallToolRequest, input MetaToolInput) (*mcp.CallToolResult, any, error)

MakeMetaHandler creates a generic MCP tool handler that dispatches to action routes. The formatResult function converts the action result into an MCP response. If formatResult is nil, a default JSON formatter is used.

Destructive actions (delete, remove, revoke, unprotect, etc.) are automatically intercepted with a user confirmation prompt via MCP elicitation before execution. Confirmation can be bypassed with GITLAB_MCP_YOLO_MODE/AUTOPILOT env vars or by passing "confirm": true in the action params.

func MarkdownCodeFence

func MarkdownCodeFence(content string) string

MarkdownCodeFence returns a backtick fence long enough to contain content: three backticks, or one more than the longest run inside it.

A fixed three-backtick fence is closed by any content that contains one, and the content here is a repository file, a job log, a snippet or a diff — written by whoever can push a branch or run a pipeline. Everything after the run they wrote renders as live Markdown at the top level of the response: headings, links, and the server's own guidance section. Sizing the fence to the body is the rule Markdown itself uses for nesting code, and it is why this helper exists rather than a literal.

func MarkdownFencedBlock

func MarkdownFencedBlock(language, content string) string

MarkdownFencedBlock renders content as a complete fenced code block: a fence sized by MarkdownCodeFence, the info string, the body, and the matching closing fence on its own line. Pass an empty language for a bare fence.

Prefer it over writing a fence by hand wherever the body is GitLab-authored.

func MarkdownForResult

func MarkdownForResult(result any) *mcp.CallToolResult

MarkdownForResult resolves a tool output to its Markdown mcp.CallToolResult. Returns nil for nil input (caller should handle), returns nil when no formatter is registered for the concrete type.

func MarkdownFormatterCount

func MarkdownFormatterCount() int

MarkdownFormatterCount returns the number of registered Markdown formatters (string + result variants). Useful for sanity checks in tests and tools that need to know "are formatters even loaded?".

func MarkdownTableHeader

func MarkdownTableHeader(columns ...string) string

MarkdownTableHeader returns a Markdown table header followed by a standard separator row for the supplied column labels.

func MarkdownTableRow

func MarkdownTableRow(cells ...string) string

MarkdownTableRow returns a Markdown table row for the supplied cell values.

func MarkdownTableSeparator

func MarkdownTableSeparator(columns int) string

MarkdownTableSeparator returns a standard left-aligned Markdown separator row for the requested number of columns.

func MdTitleLink(title, url string) string

MdTitleLink returns the title as a Markdown link if url is non-empty, otherwise returns the escaped title. Suitable for table cells. Both halves are escaped, so neither the title nor the URL can end the link they are in.

func MergeVariables

func MergeVariables(sources ...map[string]any) map[string]any

MergeVariables merges multiple variable maps into a single map. Later maps override earlier ones for duplicate keys.

func MetaActionSchema

func MetaActionSchema(route ActionRoute) map[string]any

MetaActionSchema returns the params schema served for one meta-tool action: the route's input schema with the destructive confirmation property and the parameter guidance added, or a permissive placeholder when the route captured no schema. For a route of a shared catalog the result is built once per process and shared; the caller must not mutate it.

func MetaParamSchemaMode

func MetaParamSchemaMode() string

MetaParamSchemaMode reports the meta-tool input schema strategy currently selected by SetMetaParamSchemaMode: "opaque", "compact" or "full". Callers that memoize a registered surface must key on it, because the same registration produces different input schemas under each mode.

func MetaSchemaURI

func MetaSchemaURI(tool, action string) string

MetaSchemaURI returns the resource URI for a tool/action schema.

func MetaToolDescriptionPrefix

func MetaToolDescriptionPrefix(toolName string, routes ActionMap) string

MetaToolDescriptionPrefix builds a fixed-format header that should be prepended to a meta-tool's user-supplied description. The header gives LLMs a literal JSON usage example based on a representative action and points at the gitlab://tools resource for per-action params schemas. Returns an empty string when routes is empty so callers degrade gracefully rather than emit a malformed header.

func MetaToolOutputSchema

func MetaToolOutputSchema() map[string]any

MetaToolOutputSchema returns the shared action-dispatch output schema used by meta-tools.

func MetaToolSchema

func MetaToolSchema(routes ActionMap) map[string]any

MetaToolSchema builds a JSON Schema for a meta-tool with the action field constrained to an enum of valid action names extracted from the routes map. Setting this as Tool.InputSchema ensures the LLM sees the exact list of valid actions in the schema, enabling first-try action selection.

The strategy used (opaque, compact, full) is read from the package-level mode set via SetMetaParamSchemaMode. Default is opaque. Callers that always want the opaque envelope regardless of global configuration should invoke BuildMetaToolSchema directly.

The opaque envelope is small and is compiled and cached by name downstream (see CompileToolSchemas). The compact and full envelopes embed every action's params schema and are served as maps, so they are shared as maps, one per distinct routes map (see [metaEnvelopeFor]).

func MustIndividualToolFromSpecs

func MustIndividualToolFromSpecs(specs []ActionSpec, individualName string, opts IndividualToolProjectionOptions) *mcp.Tool

MustIndividualToolFromSpecs projects an individual tool or panics on invalid registration metadata. Use it from catalog-backed startup paths where a missing spec is a programming error.

func NormalizeActionAlias

func NormalizeActionAlias(action string, routes ActionMap) string

NormalizeActionAlias returns the canonical action name for common shortened action spellings when the canonical action exists on the target meta-tool.

func NormalizeActionAliasForParams

func NormalizeActionAliasForParams(toolName, action string, params map[string]any, routes ActionMap) string

NormalizeActionAliasForParams returns the canonical action name for aliases that depend on the submitted parameter shape.

func NormalizeParamAliasesForSchema

func NormalizeParamAliasesForSchema(params, schema map[string]any) map[string]any

NormalizeParamAliasesForSchema applies the same compatibility aliases used by UnmarshalParams, driven by a JSON Schema properties map instead of a Go struct type. It is used by evaluation code that validates simulated calls.

func NormalizeText

func NormalizeText(s string) string

NormalizeText replaces literal escape sequences with real characters. MCP clients may send text with literal backslash-n instead of real newlines when the JSON transport double-escapes the input.

Replacement order matters to avoid cascading conversions:

  1. `\\` -> `\` (double-escaped backslash first, so `\\n` becomes `\` + literal n, not a newline)
  2. `\r\n` -> `\n` (CRLF before individual CR/LF to avoid double-replacement)
  3. `\r` -> `\n` (standalone carriage return)
  4. `\n` -> newline (the most common case)
  5. `\t` -> tab

func NotFoundResult

func NotFoundResult(resource, identifier string, hints ...string) *mcp.CallToolResult

NotFoundResult creates an informational MCP tool result for resources that do not exist or are not accessible. Instead of returning a Go error (which would be logged as ERROR and produce an opaque error message for the LLM), this returns a structured Markdown result with actionable next steps.

The result has IsError=true to signal the tool could not fulfill the request, but the content is rich and helpful — the LLM can act on the suggestions.

Use this in register.go handler closures when IsHTTPStatus(err, 404) is true for "get" operations. Pass nil error back to the SDK so the call is logged at INFO level instead of ERROR.

func OpenAndValidateFile

func OpenAndValidateFile(path string, maxSize int64) (*os.File, os.FileInfo, error)

OpenAndValidateFile opens a caller-supplied local file for reading after confining it to the allowed upload directories, resolving it through every symlink on the way, and validating that what the resolved path names is a regular file of at most maxSize bytes. Returns the open file handle and its FileInfo.

The containment is the point: file_path names a path on the machine the server runs on, and the caller — a model following an instruction that may have come from an issue description or a job log — is not the person whose files these are. See CanonicalLocalFilePath for the roots.

func OpenFileOrBase64Source

func OpenFileOrBase64Source(op, filePath, contentBase64 string) (reader io.Reader, size int64, cleanup func(), err error)

OpenFileOrBase64Source resolves the mutually-exclusive file_path / content_base64 upload input pair into a streaming reader plus the source size and a cleanup func the caller must defer. file_path is opened and validated against the configured upload size limit and streamed without buffering; content_base64 is decoded in memory. All errors are prefixed with op, matching the per-tool error convention.

func ParameterGuidanceIdentity

func ParameterGuidanceIdentity(guidance map[string]ParameterGuidance) string

ParameterGuidanceIdentity names a guidance map by its content, for a transform key whose output depends on the guidance as well as on the schema. Nil and empty maps name the same thing, since a transform treats them alike.

By content and not by address, which was the other way to close the same hole. DeriveSchema memoizes on the identity of the input schema, and every typed route's input schema is shared, so a route from a catalog nobody retained still writes a memo entry that lives for the process. An address in that key would be the address of a guidance map that does not: once the catalog is collected the allocator may hand the address out again, and the next map at it would be served the previous route's guidance. Pinning the guidance of every catalog marked shared would close it for the routes somebody remembered to pin and leave the rest keyed on a transient address; a digest cannot be forgotten, holds nothing alive, and lets two routes that spell the same guidance share one derivation.

The cost is a hash of a few short strings per lookup, against the schema clone the memo saves.

func ParseGID

func ParseGID(gid string) (typeName string, id int64, err error)

ParseGID extracts the type name and numeric ID from a GitLab Global ID. It returns an error if the format is invalid.

ParseGID("gid://gitlab/Vulnerability/42") → ("Vulnerability", 42, nil)

func ParseMetaSchemaURI

func ParseMetaSchemaURI(uri string) (tool, action string)

ParseMetaSchemaURI extracts the tool and action segments from a schema URI.

func ParseOptionalTime

func ParseOptionalTime(s string) *time.Time

ParseOptionalTime parses an RFC3339 string and returns a *time.Time. Returns nil if the string is empty or unparseable.

func PipelineInputsSchema

func PipelineInputsSchema[T any](property string) map[string]any

PipelineInputsSchema builds the JSON Schema for the typed input T and constrains the named map property's values to exactly the shapes BuildPipelineInputs accepts (string, number, boolean, array of strings). The generic map[string]any field alone would advertise additionalProperties:true, promising value shapes the converter rejects.

func PipelineStatusEmoji

func PipelineStatusEmoji(status string) string

PipelineStatusEmoji returns the Markdown emoji for a pipeline status.

func PopulateHints

func PopulateHints(result *mcp.CallToolResult, setter HintSetter)

PopulateHints extracts next-step hints from the Markdown content of a CallToolResult and sets them on the output struct. It is a no-op when result is nil, contains no TextContent, or has no hints section.

func ProgressReportInterval

func ProgressReportInterval(total int64) int64

ProgressReportInterval returns the byte interval between progress reports. It is the smaller of 1 MB or 5% of total, with a minimum of 64 KB.

func ReadFileOrBase64

func ReadFileOrBase64(op, filePath, contentBase64 string) (*bytes.Reader, error)

ReadFileOrBase64 resolves the mutually-exclusive file_path / content_base64 upload input pair into an in-memory bytes.Reader, for endpoints that need a seekable or length-aware body. All errors are prefixed with op.

func RegisterMarkdown

func RegisterMarkdown[T any](fn func(T) string)

RegisterMarkdown registers a Markdown string formatter for type T. Subsequent calls to MarkdownForResult with a value of type T will invoke fn and wrap the returned string in a mcp.CallToolResult.

func RegisterMarkdownPair

func RegisterMarkdownPair[A, B any](first func(A) string, second func(B) string)

RegisterMarkdownPair registers two Markdown string formatters.

func RegisterMarkdownResult

func RegisterMarkdownResult[T any](fn func(T) *mcp.CallToolResult)

RegisterMarkdownResult registers a result formatter for type T. Use this for types that need custom mcp.CallToolResult construction (e.g. uploads with image content).

func RegisterMarkdownTriple

func RegisterMarkdownTriple[A, B, C any](first func(A) string, second func(B) string, third func(C) string)

RegisterMarkdownTriple registers three Markdown string formatters.

func RegisterSurfaceToolFromSpec

func RegisterSurfaceToolFromSpec(server *mcp.Server, spec ActionSpec, opts SurfaceToolRegisterOptions)

RegisterSurfaceToolFromSpec registers one visible MCP tool by projecting an ActionSpec and executing its route handler directly.

func RegisteredMarkdownTypeNames

func RegisteredMarkdownTypeNames() []string

RegisteredMarkdownTypeNames returns the type names of all registered Markdown formatters (both string and result variants). Used by validation tests to verify sub-packages self-register their formatters.

func RequestFromContext

func RequestFromContext(ctx context.Context) *mcp.CallToolRequest

RequestFromContext extracts the MCP request from a context, or nil if absent.

func ResolveProjectWebURLs

func ResolveProjectWebURLs(ctx context.Context, projects gl.ProjectsServiceInterface, projectIDs []int64) map[int64]string

ResolveProjectWebURLs fetches the web URL for each unique project ID. Failures are silently ignored — missing URLs simply produce no links.

func ResourceTemplateVariables

func ResourceTemplateVariables(template string) ([]string, error)

ResourceTemplateVariables returns the names of the {variables} in a URI template, in order of appearance, without the RFC 6570 "+" reserved-expansion prefix. A brace that never closes, or a pair with nothing between them, is an error rather than the end of the scan: ExpandResourceURI answers such a template by embedding nothing, and a declaration that validation had let through would fail that quietly on every call.

func RichContentHint

func RichContentHint(features, webURL string) string

RichContentHint returns an informational note directing users to the GitLab web URL for full rendering when non-portable GFM features are detected. Returns an empty string when features or webURL is empty.

func SanitizeError

func SanitizeError(err error) error

SanitizeError returns err with a rendering that reflects no upstream response body, leaving the chain intact for errors.As and errors.Is.

The four wrapping helpers in this file already do this to what they wrap, but a handler is free to wrap a client-go error itself with %w, and a good many do, so for those actions nothing between the handler and the SDK ever bounded the body. Calling this at the dispatchers, where every action's error passes on its way to the model and to the "tool call failed" log line, makes the wrapping helpers defense in depth rather than the only gate.

func SchemaForRoute

func SchemaForRoute[R any]() map[string]any

SchemaForRoute returns the cached output schema for type R. Exported for use by gen_llms and audit tools.

func SchemaShared

func SchemaShared(schema any) bool

SchemaShared reports whether schema was registered with ShareSchema, or produced by DeriveSchema from a registered schema.

func SetActionTimeout

func SetActionTimeout(d time.Duration)

SetActionTimeout sets the deadline every action runs under. Zero or a negative value disables it.

func SetLocalFilesystemAccess

func SetLocalFilesystemAccess(allowed bool)

SetLocalFilesystemAccess overrides the transport inference for this process: pass false to refuse every caller-supplied local path, true to allow the allow-listed roots. Call it before any tool handler runs.

The default is inferred from the process arguments rather than configured, so a deployment that never heard of this policy still gets the right answer: an operator who forgets a flag would otherwise be the one running the exposed server. An explicit call always wins over the inference.

func SetMetaParamSchemaMode

func SetMetaParamSchemaMode(mode string)

SetMetaParamSchemaMode selects the meta-tool input schema strategy used by MetaToolSchema. Accepts "opaque" (default), "compact", or "full". Any other value is coerced to opaque so that misconfiguration cannot break the tools/list payload. Must be called before meta-tools are registered; later calls only affect schemas built after the call returns.

func SetMetaParamSchemaModeScoped

func SetMetaParamSchemaModeScoped(mode string) func()

SetMetaParamSchemaModeScoped selects the meta-tool input schema strategy and returns a restore function for tests that temporarily override the global mode.

func SetUploadConfig

func SetUploadConfig(maxFileSize int64)

SetUploadConfig overrides the default upload thresholds. Call before RegisterAll to propagate values into tool handler closures.

func ShareSchema

func ShareSchema(schema any)

ShareSchema registers a schema as process-lived so that transforms of it are memoized by DeriveSchema. Registering keeps the schema reachable for the rest of the process, which is the point: only a schema that can never be collected has an address that can serve as its identity.

Register what the process builds once and serves from then on: the reflected type schemas, the compiled tool schemas, and the routes of a catalog cached per configuration. Never register a per-server map.

func SharedSchemaIdentity

func SharedSchemaIdentity(schema any) (string, bool)

SharedSchemaIdentity returns a string naming a registered schema's identity, for callers that build a composite cache key out of several schemas. It reports false for a schema that is not shared, because the address of a map that can be collected names nothing durable.

func StripControlBytes

func StripControlBytes(s string) string

StripControlBytes removes the C0 and C1 control ranges and DEL from s, keeping the three controls Markdown actually uses: tab, newline and carriage return.

Everything this package renders is GitLab-authored: an issue title, a file, a job log, a note somebody left on a merge request. None of it is written by the caller, and a good deal of it is written by whoever can open an issue or push a branch, which on a public project is anybody. It reaches the model as text and frequently reaches a person's terminal unchanged, where an escape sequence is not text but an instruction: ESC[2J clears the screen, ESC]0;…BEL renames the window. Nothing downstream filters them, so they are dropped here, at the point the text becomes part of a response.

Dropping rather than escaping is deliberate. A rendered escape would still have to be un-rendered by something to be read, and the sequences that matter carry no information a reader loses by not seeing them: what survives of ESC[2J is "[2J", which says plainly that the content tried something.

This covers the text channel and not structuredContent, and that is a decision rather than an oversight. structuredContent is marshaled by the SDK from the typed value each handler returns, so there is no funnel in this package to apply it at: closing it means either a sweep over every output struct in the 175 handler packages, or a result middleware in the server that walks each value reflectively on the way out. The second is the tempting one and it is the wrong trade. encoding/json already escapes every byte in these ranges, so what reaches a client is the six characters of a \u001b sequence, which no terminal acts on: a viewer would have to parse the JSON and print a string field raw to be affected, and such a client is showing the text channel too, which is sanitized here. Paying a reflective walk of every result for that is not worth it. If the shape of the risk changes, the place to add it is one middleware at the server boundary, not this function.

func StripMetaToolDescriptionPrefix

func StripMetaToolDescriptionPrefix(description string) string

StripMetaToolDescriptionPrefix removes the generated meta-tool usage header added by MetaToolDescriptionPrefix while preserving standalone descriptions that happen to start with an example.

func SuccessResult

func SuccessResult(markdown string) *mcp.CallToolResult

SuccessResult builds a standard success mcp.CallToolResult with Markdown and the structured output for both human-readable and programmatic consumption. If markdown is empty, the result contains only a structured JSON annotation.

func TitleFromName

func TitleFromName(name string) string

TitleFromName generates a human-readable UI title from a snake_case MCP tool name by stripping the "gitlab_" prefix and converting to Title Case.

TitleFromName("gitlab_list_projects") // returns "List Projects"
TitleFromName("my_open_mrs")          // returns "My Open MRs"

func ToolResultAnnotated

func ToolResultAnnotated(md string, ann *mcp.Annotations) *mcp.CallToolResult

ToolResultAnnotated wraps a Markdown string into a CallToolResult with content annotations that guide MCP clients on audience and priority. Pass nil annotations to get the same behavior as ToolResultWithMarkdown.

func ToolResultWithImage

func ToolResultWithImage(md string, ann *mcp.Annotations, imageData []byte, mimeType string) *mcp.CallToolResult

ToolResultWithImage creates a CallToolResult containing both a text description (metadata) and an ImageContent block with the raw image bytes. Multimodal LLMs can "see" the image; text-only LLMs get the metadata.

func ToolResultWithMarkdown

func ToolResultWithMarkdown(md string) *mcp.CallToolResult

ToolResultWithMarkdown wraps a Markdown string into a CallToolResult with a single TextContent entry annotated for assistant-only audience. This prevents MCP clients (e.g. VS Code) from displaying raw Markdown inline — the LLM processes it and presents formatted output to the user.

Control characters are dropped here as well as in the helpers that build the Markdown, because this is the last point every rendered response passes through and a formatter that writes a GitLab field straight into its builder would otherwise deliver an escape sequence to whatever prints the text. See StripControlBytes.

func TypeIdentity

func TypeIdentity(rt reflect.Type) string

TypeIdentity names a reflected type unambiguously, package path included, since two packages may declare input types with the same name. It is the form a transform name embeds when the transform depends on a type.

func UnattributedRequestError

func UnattributedRequestError() error

UnattributedRequestError is UnattributedRequestMessage as a JSON-RPC internal error, for the surfaces that answer with an error value rather than a classified string.

Internal rather than invalid-request for the reason the message gives: the request was well formed and this server failed to route it.

func UnattributedRequestErrorFor

func UnattributedRequestErrorFor(ctx context.Context) error

UnattributedRequestErrorFor is UnattributedRequestError for a request that is still live, and the reason it ended for one that is not.

Not every unattributed request is a wiring defect, which is what the message says it is. A POST the client abandoned takes its carrier with it, and the carrier is where the credential is read from, so the binding finds nothing and the handler resolves the credential-less client: a legitimate cause, answered with a sentence asking the caller to report a bug. The tools path never had this problem, because ClassifyError checks cancellation first and that check is what it hits.

Consulting the context is what tells them apart. A cancelled request is over and nobody is reading the answer, so what matters is only that it is not blamed on the wiring in a log an operator does read.

One cause it cannot distinguish, and does not claim to: the pool evicting the entry between the gate resolving it and the gate looking up its state. The request is alive and unattributable, and the honest thing to tell that caller is exactly what the message already says, since retrying rebuilds the entry.

func UnmarshalParams

func UnmarshalParams[T any](params map[string]any) (T, error)

UnmarshalParams re-serializes params map to JSON and deserializes into T. LLMs frequently send numeric values as JSON strings (e.g. "17" instead of 17). When standard unmarshalling fails, this function retries after coercing string values that look like integers or floats into actual numbers.

Unknown keys in params (i.e. fields that do not exist on T) are rejected with an actionable error so that LLMs receive a clear diagnostic when they mistype a parameter name (e.g. "iid" instead of "snippet_id"). This mirrors the JSON Schema lockdown applied to tools/list responses (see LockdownInputSchemas) and the MCP guidance to surface validation errors as recoverable tool results so the model can self-correct. Meta-protocol keys (see [reservedParamKeys]) are stripped before unmarshalling.

func ValidActionsString

func ValidActionsString(routes ActionMap) string

ValidActionsString returns a sorted, comma-separated list of action names.

func ValidateDiffPosition

func ValidateDiffPosition(diffLines []DiffLine, newLine, oldLine int) error

ValidateDiffPosition checks whether a (newLine, oldLine) combination corresponds to a valid commentable position in the parsed diff lines.

Rules enforced (per GitLab API):

  • new_line only → line must be an added (+) line
  • old_line only → line must be a removed (-) line
  • both set → line must be an unchanged context line

Returns nil when the position is valid, or a descriptive error explaining exactly why the position is invalid and what the caller should do instead.

func ValidatePackageFileName

func ValidatePackageFileName(filename string) error

ValidatePackageFileName validates a filename for GitLab generic package upload. Filenames must not be empty, must not contain spaces, and must not start with a tilde or at-sign.

func ValidatePackageName

func ValidatePackageName(name string) error

ValidatePackageName validates a GitLab generic package name against allowed characters. Names must start with a letter or digit and may contain A-Z a-z 0-9 . _ - + ~ / @.

func ValidateRateLimit

func ValidateRateLimit(rps float64, burst int) error

ValidateRateLimit reports whether the given rps/burst pair forms a well-defined limiter configuration. Used by the server entrypoint to fail fast on bad CLI input rather than silently disabling the limiter.

func ValidateRouteBinding

func ValidateRouteBinding(route ActionRoute) error

ValidateRouteBinding reports a route whose handler was replaced without its binder being replaced with it. Such a route serves the replacement on the server it was built for and the constructor's plain handler on every server a shared catalog rebinds it to, which is a defect no test of the domain package can see, since the package tests the route as it built it. The check is exact: the handler in place must be the very closure the binder installed, compared by identity rather than by the code it runs, because the compiler gives an inlined copy of a function literal a closure of its own.

A route with no binder at all passes here, because this compares the two halves against each other and there is nothing to compare. Whether a route is allowed to have no binder is a question about where the route lives, and is answered where that is known: the catalog requires one of every action but the dynamic controllers, which close over a registry rather than a client.

func WithActionDeadline

func WithActionDeadline(ctx context.Context) (context.Context, context.CancelFunc)

WithActionDeadline derives the context an action runs under: the caller's, bounded by the configured deadline when there is one. The cancel function is always safe to call.

Exported for the one model-facing handler that is not a catalog action and so passes through none of the WrapAction functions: the dynamic surface's gitlab_find_action, whose cost is the catalog rather than a GitLab call and which was therefore the only registered tool with no deadline at all.

func WithHints

func WithHints[O any](result *mcp.CallToolResult, out O, err error) (*mcp.CallToolResult, O, error)

WithHints extracts hints from a CallToolResult and populates them on the typed output struct, returning all three handler values in one call. This avoids evaluation-order ambiguity in multi-value return statements.

For value Out types (the common case), &out is used internally to satisfy the HintSetter pointer receiver. For pointer Out types (*T), the pointer itself implements HintSetter. If neither case applies, WithHints is a no-op.

return toolutil.WithHints(toolutil.ToolResultWithMarkdown(md), out, err)

func WithInternalInspection

func WithInternalInspection(ctx context.Context) context.Context

WithInternalInspection marks ctx as belonging to a session the server opens against itself, so what it asks for is not charged to a caller's bucket.

The server lists its own tools several times while it starts: to count them, to drop the excluded ones, to learn which survived the read-only and safe-mode passes, to build the gitlab://tools manifest, and to write the server card. Those requests travel the same receiving middlewares a client's do, so metering tools/list charged them to the deployment's own bucket, and on a server started with --rate-limit-burst=1 the second one was refused and the tool manifest resource failed to build. Pass this to the server's mcp.Server.Connect: the handler context descends from that one, and no header or parameter a caller controls reaches it.

Only the catalog bucket consults the mark, because only listings are asked for this way. A method that reached GitLab would be spending the credential whether the server or a client asked for it, and should still be charged.

func WrapErr

func WrapErr(operation string, err error) error

WrapErr classifies the error, enriches it with a semantic message, and wraps it with the operation name. All tool handlers funnel through here so connectivity and auth problems are reported consistently.

func WrapErrWithHint

func WrapErrWithHint(operation string, err error, hint string) error

WrapErrWithHint works like WrapErrWithMessage but appends an actionable hint that tells the LLM what to do next. Example:

"branchDelete: bad request (Cannot delete: protected branch).
 Suggestion: use gitlab_branch_unprotect first, then retry deletion: <original>"

The hint should be a concise suggestion starting with a verb (e.g., "use gitlab_branch_list to verify the branch name").

func WrapErrWithMessage

func WrapErrWithMessage(operation string, err error) error

WrapErrWithMessage works like WrapErr but also includes the specific GitLab error message (from ErrorResponse.Message) when available. This produces richer errors like:

"fileCreate: bad request (A file with this name already exists): POST .../files: 400"

Use WrapErrWithMessage for mutating operations where the specific GitLab error detail helps the LLM understand what went wrong. Use WrapErr for read-only operations where the generic classification suffices.

func WrapErrWithStatusHint

func WrapErrWithStatusHint(operation string, err error, code int, hint string) error

WrapErrWithStatusHint returns WrapErrWithHint(operation, err, hint) when err matches the given HTTP status code, otherwise falls back to WrapErrWithMessage(operation, err). It compresses the common pattern:

if toolutil.IsHTTPStatus(err, 404) {
    return ..., toolutil.WrapErrWithHint(op, err, hint)
}
return ..., toolutil.WrapErrWithMessage(op, err)

into a single call. For handlers that need different hints per status, use a switch over IsHTTPStatus checks; this helper covers the dominant single- status case.

func WrapGFMBody

func WrapGFMBody(body string) string

WrapGFMBody wraps user-generated GFM content in a Markdown blockquote to prevent heading hierarchy conflicts and structural breaks in the formatted output. Empty bodies return an empty string.

The quote is the containment: every line of the body is a line of a quote, so the body cannot add a heading, a list item or a section to the document it sits in. Two things happen before it. Control characters are dropped, and the server's own guidance heading is defused, so a body carrying that heading is shown as the text it is rather than parsed back out as the server's suggestions. See DefuseHintsHeading.

func WriteDescription

func WriteDescription(b *strings.Builder, description string)

WriteDescription writes a GitLab-authored description field.

A description is prose somebody typed into GitLab, and the "- **Description**: %s" line it used to be interpolated into puts it at column zero after a list bullet: its second line is no longer part of the item, so an embedded heading is a heading of the response and an embedded bullet is an item of the server's own list. A one-line description keeps the compact form with the cell escaping applied; anything longer becomes a blockquote, which is what the merge request, wiki and release renderers already do.

func WriteEmpty

func WriteEmpty(b *strings.Builder, resource string)

WriteEmpty writes a standardized empty-result message to the builder. The resource parameter should be a clear, specific plural noun (e.g. "merge requests", "pipeline variables", "protected branches").

func WriteHints

func WriteHints(b *strings.Builder, hints ...string)

WriteHints appends a "💡 Next steps" section to the Markdown builder. Each hint is a short string describing a related action the LLM can take (e.g. "Use action 'delete' to remove this package"). If no hints are provided, no section is written.

Whether or not there are hints, any guidance heading already in the builder is defused first. This is the one place every formatter passes through on its way to a response — including the raw file, job trace, snippet and discussion renderers that embed GitLab bytes verbatim — so doing it here covers them all without each renderer having to remember, and covers the ones added later.

func WriteListSummary

func WriteListSummary(b *strings.Builder, shown int, p PaginationOutput)

WriteListSummary appends a brief "Showing N of M results (page X of Y)" line between the heading and the table body. It is a no-op when there is only a single page, because the heading count already conveys everything.

func WriteMdURL

func WriteMdURL(b *strings.Builder, url string)

WriteMdURL appends the "- **URL**: ..." line, rendering url as a link whose label and destination are the same address.

It replaces a pair of format constants that read "- **URL**: [%[1]s](%[1]s)" and were used at 31 call sites. One value filling both halves of a link is a shape no argument can be escaped into safely, so each of those call sites hand-wrote a link with nothing in front of it, and the audit flagged every one of them twice. Escaping belongs here, once, rather than in a decision each of 22 packages makes for itself.

func WriteMdURLNewline

func WriteMdURLNewline(b *strings.Builder, url string)

WriteMdURLNewline appends the same line as WriteMdURL, preceded by a blank line, for a formatter that closes a section with it.

func WritePagination

func WritePagination(b *strings.Builder, p PaginationOutput)

WritePagination appends a newline-wrapped pagination summary to the builder.

Types

type AccessRequestExtra

type AccessRequestExtra struct {
	MemberExtra
	AvatarURL string            `json:"avatar_url"`
	WebURL    string            `json:"web_url"`
	CreatedBy *MemberUserOutput `json:"created_by"`
	// ExpiresAt is a date and not a timestamp, which is how GitLab spells a
	// membership expiry, so it is read as the string it arrives as.
	ExpiresAt  string            `json:"expires_at"`
	Email      string            `json:"email"`
	MemberRole *MemberRoleOutput `json:"member_role"`
}

AccessRequestExtra is what lib/api/entities/access_requester.rb sends on an access request that client-go's AccessRequest does not carry. The entity inherits Member and merges UserBasic into it, so everything MemberExtra reads arrives here too; these are the keys beside them. The two URLs are unconditional; the address, the creator and the custom role each wait on their own condition, and the expiry is sent on every member.

Three keys the entity can send are deliberately not read: is_using_seat, avatar_path and custom_attributes wait on presenter options the caller has to ask for, and no access-request route declares show_seat_info, only_path or with_custom_attributes, so GitLab never sends them here. The first is absent from this shape and the other two arrive through the embed unread.

func CapturedAccessRequest

func CapturedAccessRequest(capture *gitlabclient.ResponseCapture) (AccessRequestExtra, error)

CapturedAccessRequest reads them off the captured answer to a request that returned one access request.

func CapturedAccessRequests

func CapturedAccessRequests(capture *gitlabclient.ResponseCapture, decoded int) ([]AccessRequestExtra, error)

CapturedAccessRequests reads the same off a list answer, one extra per request in order, the count held to what the SDK decoded.

type ActionAliasSpec

type ActionAliasSpec struct {
	Alias          string
	Target         string
	Source         string
	Searchable     bool
	Deprecated     bool
	RemovalVersion string
	Reason         string
}

ActionAliasSpec describes a compatibility alias that resolves to a canonical action owned by an ActionSpec.

type ActionFunc

type ActionFunc func(ctx context.Context, params map[string]any) (any, error)

ActionFunc is a handler that receives raw params and returns a result or error.

func SafeModeActionFunc

func SafeModeActionFunc(name string) ActionFunc

SafeModeActionFunc returns an ActionFunc that returns a SafeModePreview for name instead of executing anything. It is used to neutralize mutating catalog actions at registration time, so dispatcher surfaces (meta-tools and the dynamic execute tool) preview each mutating action individually while their read-only actions keep executing.

func WrapAction

func WrapAction[T, R any](client *gitlabclient.Client, fn func(ctx context.Context, client *gitlabclient.Client, input T) (R, error)) ActionFunc

WrapAction wraps a typed handler (input T -> output R) into a generic ActionFunc.

The client the handler runs under is the one bound to the request context, falling back to the one this route was built for. That is the seam a server shared by several credentials needs: its routes are bound to the unbound client, which refuses every request, and the per-request binding is what makes a call reach the caller's own instance. On stdio, and everywhere a catalog is built for one real client, no context carries a binding and the captured client is returned unchanged.

func WrapActionWithRequest

func WrapActionWithRequest[T, R any](client *gitlabclient.Client, fn func(ctx context.Context, req *mcp.CallToolRequest, client *gitlabclient.Client, input T) (R, error)) ActionFunc

WrapActionWithRequest wraps a handler that also requires the MCP request (e.g., for progress tracking). The request is extracted from context via RequestFromContext; if absent, nil is passed.

func WrapVoidAction

func WrapVoidAction[T any](client *gitlabclient.Client, fn func(ctx context.Context, client *gitlabclient.Client, input T) error) ActionFunc

WrapVoidAction wraps a typed handler that returns only error.

It carries the same deadline as WrapAction, and for a stronger reason: an action that returns nothing is a delete, a cancel or a retry, which is exactly the call an operator wants bounded. The deadline was missing here while the setting's own documentation said every action passed through it.

func WrapVoidActionWithRequest

func WrapVoidActionWithRequest[T any](client *gitlabclient.Client, fn func(ctx context.Context, req *mcp.CallToolRequest, client *gitlabclient.Client, input T) error) ActionFunc

WrapVoidActionWithRequest wraps a void handler that also requires the MCP request. The request is extracted from context via RequestFromContext; if absent, nil is passed.

type ActionMap

type ActionMap map[string]ActionRoute

ActionMap maps action names to their route definitions (handler + metadata).

func ActionSpecsToMap

func ActionSpecsToMap(specs []ActionSpec) ActionMap

ActionSpecsToMap converts canonical action specs to a legacy ActionMap.

func ActionSpecsToMapWithError

func ActionSpecsToMapWithError(specs []ActionSpec) (ActionMap, error)

ActionSpecsToMapWithError converts canonical action specs to a legacy ActionMap.

type ActionMetaEntry

type ActionMetaEntry struct {
	// Usage replaces the generic action Usage line when non-empty.
	Usage string
	// Aliases replaces the default tool-name alias list when non-empty.
	Aliases []string
	// Related replaces RelatedActions when non-empty.
	Related []string
	// Guidance replaces ParameterGuidance when non-empty.
	Guidance map[string]ParameterGuidance
	// Description sets the individual-tool description when non-empty.
	Description string
}

ActionMetaEntry holds the per-action discovery metadata that domains overlay on top of their shared ActionSpecOptions defaults: a non-generic Usage line, distinctive natural-language Aliases, canonical RelatedActions, per-parameter Guidance, and the "Returns: … See also: …" individual-tool Description (1:1 audit R-META). Every GitLab domain that carries an action metadata table uses this single type instead of a domain-local struct, and applies it with ApplyActionMeta, so the overlay logic lives in one place.

Zero-value fields are skipped by ApplyActionMeta; only fields a domain actually populates override the option defaults.

type ActionRoute

type ActionRoute struct {
	Handler ActionFunc
	// Bind builds the handler for a client. Nil means the handler depends on
	// no client and is shared as it is.
	Bind func(*gitlabclient.Client) ActionFunc

	Destructive       bool
	InputType         reflect.Type
	OutputType        reflect.Type
	InputSchema       map[string]any
	OutputSchema      map[string]any
	ParameterGuidance map[string]ParameterGuidance
	Aliases           []string
	Tags              []string
	Usage             string
	RelatedActions    []string
	// EmbeddedResource is the canonical resource URI template the dispatchers
	// expand from the call's parameters and embed in a successful result. The
	// catalog sets it from the action's spec when the spec's policy embeds.
	EmbeddedResource string
	// contains filtered or unexported fields
}

ActionRoute pairs an action handler with metadata about its behavior. Used by meta-tools to carry per-route destructive classification without string parsing. OutputSchema holds the JSON Schema for the action's typed output. InputSchema holds the JSON Schema for the action's typed params (nil for routes constructed via the untyped Route and DestructiveRoute constructors).

A route is two things with different lifetimes. Its metadata (the schemas, the guidance, the aliases) depends on nothing but the action, and a catalog cached per configuration shares it between every server in the process; the schema maps are frozen, and a consumer that needs to change one copies it first (see DeriveSchema). Its Handler is bound to one GitLab client, and so to one credential. Bind is what joins the two: the RouteAction family sets it to rebuild the handler for any client, and ActionRoute.BindTo pairs the shared metadata with a handler for the client at hand. A route with no Bind has a handler that captured no client and is served as is.

Replacing Handler directly breaks that pairing silently: the shared catalog would rebind to the constructor's plain handler and drop whatever the replacement added. ActionRoute.WrapHandler and ActionRoute.WithBoundHandler change the handler and its binder together, and ValidateRouteBinding refuses a route whose two halves disagree.

func CloneActionRoute

func CloneActionRoute(route ActionRoute) ActionRoute

CloneActionRoute returns a copy of the route that owns its string slices and shares everything else. The handler, the types and the frozen schema and guidance maps describe the same action and are not copied; the Aliases, Tags and RelatedActions slices are, so a caller may append to them without reaching the original.

func DestructiveAction

func DestructiveAction[T, R any](client *gitlabclient.Client, fn func(ctx context.Context, client *gitlabclient.Client, input T) (R, error)) ActionRoute

DestructiveAction wraps a typed function as a destructive ActionRoute and attaches input/output schemas.

func DestructiveActionWithRequest

func DestructiveActionWithRequest[T, R any](client *gitlabclient.Client, fn func(ctx context.Context, req *mcp.CallToolRequest, client *gitlabclient.Client, input T) (R, error)) ActionRoute

DestructiveActionWithRequest wraps a typed function that needs the MCP request as a destructive ActionRoute and attaches input/output schemas.

func DestructiveFunc

func DestructiveFunc[T, R any](fn func(ctx context.Context, input T) (R, error)) ActionRoute

DestructiveFunc wraps a typed function as a destructive ActionRoute without a GitLab client dependency and attaches input and output schemas.

func DestructiveRoute

func DestructiveRoute(fn ActionFunc) ActionRoute

DestructiveRoute creates a destructive ActionRoute without an output schema.

func DestructiveVoidAction

func DestructiveVoidAction[T any](client *gitlabclient.Client, fn func(ctx context.Context, client *gitlabclient.Client, input T) error) ActionRoute

DestructiveVoidAction wraps a typed void function as a destructive ActionRoute. The handler returns a typed DeleteOutput confirmation so meta-tool routes expose structured output instead of nil content.

func DestructiveVoidActionWithRequest

func DestructiveVoidActionWithRequest[T any](client *gitlabclient.Client, fn func(ctx context.Context, req *mcp.CallToolRequest, client *gitlabclient.Client, input T) error) ActionRoute

DestructiveVoidActionWithRequest wraps a request-aware void function as a destructive ActionRoute with typed DeleteOutput confirmation, reusing WrapVoidActionWithRequest so the request-extraction logic is not duplicated.

func Route

func Route(fn ActionFunc) ActionRoute

Route creates a non-destructive ActionRoute without an output schema.

func RouteAction

func RouteAction[T, R any](client *gitlabclient.Client, fn func(ctx context.Context, client *gitlabclient.Client, input T) (R, error)) ActionRoute

RouteAction wraps a typed function as a non-destructive ActionRoute and attaches the JSON Schema for the input type T and output type R.

func RouteActionWithRequest

func RouteActionWithRequest[T, R any](client *gitlabclient.Client, fn func(ctx context.Context, req *mcp.CallToolRequest, client *gitlabclient.Client, input T) (R, error)) ActionRoute

RouteActionWithRequest wraps a typed function that needs the MCP request as a non-destructive ActionRoute and attaches input/output schemas.

func RouteFunc

func RouteFunc[T, R any](fn func(ctx context.Context, input T) (R, error)) ActionRoute

RouteFunc wraps a typed function as a non-destructive ActionRoute without a GitLab client dependency and attaches input and output schemas.

func RouteRequestFunc

func RouteRequestFunc[T, R any](fn func(ctx context.Context, req *mcp.CallToolRequest, input T) (R, error)) ActionRoute

RouteRequestFunc wraps a typed request-aware function as a non-destructive ActionRoute without a GitLab client dependency and attaches schemas.

func RouteVoidAction

func RouteVoidAction[T any](client *gitlabclient.Client, fn func(ctx context.Context, client *gitlabclient.Client, input T) error) ActionRoute

RouteVoidAction wraps a typed void function as a non-destructive ActionRoute. The handler returns a typed VoidOutput confirmation so meta-tool routes expose structured output instead of nil content.

func (ActionRoute) BindTo

func (route ActionRoute) BindTo(client *gitlabclient.Client) ActionRoute

BindTo returns the route with its handler rebuilt for client. A route whose Bind is nil captured no client and is returned unchanged. The metadata is shared with the receiver, not copied: it is the same action.

func (ActionRoute) WithAliases

func (route ActionRoute) WithAliases(aliases ...string) ActionRoute

WithAliases returns a copy of route with additional search aliases.

func (ActionRoute) WithBoundHandler

func (route ActionRoute) WithBoundHandler(client *gitlabclient.Client, bind func(*gitlabclient.Client) ActionFunc) ActionRoute

WithBoundHandler returns the route with a handler that bind builds for client, and with bind recorded so a shared catalog can build the same handler for any other client. Use it where the replacement handler needs the client itself rather than only the handler it replaces.

func (ActionRoute) WithParameterGuidance

func (route ActionRoute) WithParameterGuidance(guidance map[string]ParameterGuidance) ActionRoute

WithParameterGuidance returns a copy of route with merged parameter guidance.

func (ActionRoute) WithRelatedActions

func (route ActionRoute) WithRelatedActions(actions ...string) ActionRoute

WithRelatedActions returns a copy of route with related canonical action IDs.

func (ActionRoute) WithTags

func (route ActionRoute) WithTags(tags ...string) ActionRoute

WithTags returns a copy of route with additional search tags.

func (ActionRoute) WithUsage

func (route ActionRoute) WithUsage(usage string) ActionRoute

WithUsage returns a copy of route with a short model-facing usage hint.

func (ActionRoute) WrapHandler

func (route ActionRoute) WrapHandler(wrap func(next ActionFunc) ActionFunc) ActionRoute

WrapHandler returns the route with wrap applied to its handler, now and on every later rebinding. Use it where a domain decorates the constructor's handler, such as turning a 404 into a structured not-found result: wrap receives the handler to decorate and returns the decorated one.

type ActionSpec

type ActionSpec struct {
	Name                   string
	Route                  ActionRoute
	Aliases                []string
	Tags                   []string
	Usage                  string
	RelatedActions         []string
	Compatibility          CompatibilityPolicy
	ParameterGuidance      map[string]ParameterGuidance
	InputSchemaOverrides   []InputSchemaOverride
	ReadOnly               bool
	Destructive            bool
	Idempotent             bool
	OpenWorld              bool
	Edition                string
	GitLabDotComOnly       bool
	OwnerPackage           string
	IndividualTool         IndividualToolSpec
	ContentKind            string
	NotFoundPolicy         string
	EmbeddedResourcePolicy string
	// EmbeddedResource is the canonical gitlab:// URI template of the entity a
	// get-style action returns, expanded from the call's parameters and
	// embedded in the result under the embedded-resource policy.
	EmbeddedResource       string
	RichResultPolicy       string
	SchemaValidationNotes  []string
	RuntimeValidationNotes []string
}

ActionSpec is the canonical metadata contract for one GitLab action.

func CloneActionSpec

func CloneActionSpec(spec ActionSpec) ActionSpec

CloneActionSpec returns a defensive copy of spec and all mutable metadata it owns.

func CloneActionSpecs

func CloneActionSpecs(specs []ActionSpec) []ActionSpec

CloneActionSpecs returns defensive copies of specs in their original order.

func FillScopeParameterGuidance

func FillScopeParameterGuidance(specs []ActionSpec) []ActionSpec

FillScopeParameterGuidance returns a copy of specs with default ParameterGuidance entries added for every scope-suggestive parameter (project_id, group_id, user_id, instance_id, namespace_id, milestone_id, epic_id, ref, branch, tag, sha, path, iid) present in each spec's input schema that lacks an explicit guidance entry. The defaults use the canonical SemanticRole for each scope name and the standard "Project ID or URL-encoded path…" / "Branch or tag name…" ValueSource text.

Packages call this from their ActionSpecs() function before returning so that the discovery auditor's missing_parameter_guidance check is satisfied without each action having to hand-write guidance for every common scope parameter. Explicit guidance entries on a spec are preserved unchanged (they take precedence over the defaults).

func FillScopeParameterGuidanceSingle

func FillScopeParameterGuidanceSingle(spec ActionSpec) ActionSpec

FillScopeParameterGuidanceSingle is the single-spec form of FillScopeParameterGuidance. The central tier filter calls this after pruning tier-restricted fields from the input schema so that the default guidance set never references a field the filter has stripped. Existing guidance entries whose parameter has been removed from the schema are dropped, while guidance for scope-suggestive parameters still present in the schema is added when missing. Explicit guidance entries written by package authors are preserved unchanged.

func NewActionSpec

func NewActionSpec(name string, route ActionRoute, opts ActionSpecOptions) ActionSpec

NewActionSpec creates a defensive canonical action specification.

The route's input schema is not changed in place: the overrides and the canonical enums, formats and ranges are applied to a derived schema, built once per process for a shared input (see DeriveSchema) and privately otherwise. The transform is idempotent, so a spec cloned through this constructor keeps the schema it already had.

func NewAdditiveActionSpec

func NewAdditiveActionSpec(name string, route ActionRoute, opts ActionSpecOptions) ActionSpec

NewAdditiveActionSpec creates a mutating action whose repetition changes the result, so idempotentHint stays false.

It exists because "update" is the wrong shape for some endpoints that look like one. Logging spent time is the example: GitLab's add_spent_time is additive, so two calls of "1h" leave two hours logged, and an idempotentHint of true would invite a model to retry a call whose outcome it could not observe — leaving the user's timesheet wrong in a way nobody notices for a while.

The hint is advisory, which is exactly why it has to be accurate: nothing enforces it, so its only effect is on what a model decides to do.

func NewCreateActionSpec

func NewCreateActionSpec(name string, route ActionRoute, opts ActionSpecOptions) ActionSpec

NewCreateActionSpec creates a mutating, non-idempotent action specification.

func NewDeleteActionSpec

func NewDeleteActionSpec(name string, route ActionRoute, opts ActionSpecOptions) ActionSpec

NewDeleteActionSpec creates a destructive, idempotent action specification.

func NewReadActionSpec

func NewReadActionSpec(name string, route ActionRoute, opts ActionSpecOptions) ActionSpec

NewReadActionSpec creates a read-only, idempotent action specification.

func NewUpdateActionSpec

func NewUpdateActionSpec(name string, route ActionRoute, opts ActionSpecOptions) ActionSpec

NewUpdateActionSpec creates a mutating, idempotent action specification.

func (ActionSpec) Validate

func (spec ActionSpec) Validate() error

Validate verifies invariants that must hold before projecting a spec.

func (ActionSpec) WithEmbeddedResource

func (spec ActionSpec) WithEmbeddedResource(template string) ActionSpec

WithEmbeddedResource returns the spec with its canonical resource declared: the given gitlab:// URI template, expanded from the call's parameters, is embedded in every successful result. Declared at the spec site so the action's owner package states which resource a get returns, and the catalog validator holds the template to the action's parameters.

type ActionSpecOptions

type ActionSpecOptions struct {
	Aliases                []string
	Tags                   []string
	Usage                  string
	RelatedActions         []string
	Compatibility          CompatibilityPolicy
	ParameterGuidance      map[string]ParameterGuidance
	InputSchemaOverrides   []InputSchemaOverride
	ReadOnly               bool
	Destructive            bool
	Idempotent             bool
	OpenWorld              bool
	Edition                string
	GitLabDotComOnly       bool
	OwnerPackage           string
	IndividualTool         IndividualToolSpec
	ContentKind            string
	NotFoundPolicy         string
	EmbeddedResourcePolicy string
	EmbeddedResource       string
	RichResultPolicy       string
	SchemaValidationNotes  []string
	RuntimeValidationNotes []string
}

ActionSpecOptions contains optional metadata for NewActionSpec.

type AppearanceExtra

type AppearanceExtra struct {
	SiteName string `json:"site_name"`
}

AppearanceExtra is the instance's site name, which GitLab's appearance entity exposes under no condition.

func CapturedAppearance

func CapturedAppearance(capture *gitlabclient.ResponseCapture) (AppearanceExtra, error)

CapturedAppearance reads it off the captured answer to an appearance request.

type ApproverIDsFilter

type ApproverIDsFilter []StringOrInt

ApproverIDsFilter mirrors GitLab's approver_ids and approved_by_ids merge request filters, which accept either a list of numeric user IDs or exactly one of the literals "Any" and "None".

Elements are StringOrInt, so both JSON numbers and JSON strings unmarshal cleanly. Actions exposing this filter must widen the published item type with SchemaApproverIDsOverride, otherwise input validation rejects the literals before the handler ever sees them.

func (ApproverIDsFilter) ApproverIDsValue

func (f ApproverIDsFilter) ApproverIDsValue() (*gl.ApproverIDsValue, error)

ApproverIDsValue converts the filter into the SDK value expected by the merge request list options. It returns nil for an empty filter, so callers can assign the result unconditionally.

The literals are only meaningful on their own: GitLab has no notion of "None plus these IDs", so mixing them with user IDs is rejected rather than silently dropped, which would return a differently-filtered result set than the caller asked for.

type AssetLinkOutput

type AssetLinkOutput struct {
	ID             int64  `json:"id"`
	Name           string `json:"name"`
	URL            string `json:"url"`
	DirectAssetURL string `json:"direct_asset_url,omitempty"`
	External       bool   `json:"external"`
	LinkType       string `json:"link_type"`
}

AssetLinkOutput mirrors gl.ReleaseLink (the per-link object inside release.assets.links). The DirectAssetURL field is omitempty because the official assets-by-link endpoint only populates it for package-link types.

type AssetSourceOutput

type AssetSourceOutput struct {
	Format string `json:"format"`
	URL    string `json:"url"`
}

AssetSourceOutput mirrors gl.ReleaseAssetsSource (the per-source object inside release.assets.sources). Format is a short string (e.g. "package", "image", "url", "other").

type AssetsOutput

type AssetsOutput struct {
	Count            int64               `json:"count"`
	Sources          []AssetSourceOutput `json:"sources,omitempty"`
	Links            []AssetLinkOutput   `json:"links,omitempty"`
	EvidenceFilePath string              `json:"evidence_file_path,omitempty"`
}

AssetsOutput mirrors gl.ReleaseAssets (the count / sources / links / evidence_file_path object).

func NewAssetsOutput

func NewAssetsOutput(a gl.ReleaseAssets) *AssetsOutput

NewAssetsOutput converts a gl.ReleaseAssets into the canonical-key assets object, returning nil when the SDK value has every field zero.

type AuthorOutput

type AuthorOutput struct {
	ID        int64  `json:"id"`
	Username  string `json:"username"`
	Name      string `json:"name"`
	State     string `json:"state,omitempty"`
	AvatarURL string `json:"avatar_url"`
	WebURL    string `json:"web_url"`
}

AuthorOutput mirrors gl.BasicUser for the release author (per the documented release-by-tag response; the author object is the compact BasicUser shape, not the full User shape).

func NewAuthorOutputFromBasicUser

func NewAuthorOutputFromBasicUser(u gl.BasicUser) AuthorOutput

NewAuthorOutputFromBasicUser converts a gl.BasicUser into the canonical-key author object.

type BasicGroupDetailsOutput

type BasicGroupDetailsOutput struct {
	ID     int64  `json:"id"`
	Name   string `json:"name"`
	WebURL string `json:"web_url,omitempty"`
}

BasicGroupDetailsOutput is the group reference GitLab renders on a board, which carries three keys and not a whole group.

type BasicUserOutput

type BasicUserOutput struct {
	ID        int64  `json:"id"`
	Username  string `json:"username"`
	Name      string `json:"name"`
	State     string `json:"state"`
	AvatarURL string `json:"avatar_url"`
	WebURL    string `json:"web_url"`
	CreatedAt string `json:"created_at,omitempty"`
}

BasicUserOutput mirrors gl.BasicUser (the user shape returned in many pipeline / merge-train / deployment-MR sub-objects: head_pipeline.user, merge_train.pipeline.user, etc.).

func NewBasicUserOutput

func NewBasicUserOutput(u *gl.BasicUser) *BasicUserOutput

NewBasicUserOutput converts a *gl.BasicUser into the canonical-key basic user object, returning nil when the SDK value is nil.

func NewBasicUserOutputs

func NewBasicUserOutputs(users []*gl.BasicUser) []*BasicUserOutput

NewBasicUserOutputs converts a []*gl.BasicUser slice, skipping nil elements and returning nil for an empty input.

type BillableMemberExtra

type BillableMemberExtra struct {
	Locked      bool   `json:"locked"`
	PublicEmail string `json:"public_email"`
}

BillableMemberExtra is what ee/lib/api/entities/billable_member.rb sends on a billable member that client-go's BillableGroupMember does not carry: whether the account is locked and the address the user publishes, both of them from the UserBasic the entity inherits and both on every member.

The same UserBasic exposes avatar_path and custom_attributes, and neither is read: both wait on a presenter option, and the billable members route declares neither only_path nor with_custom_attributes, so GitLab has never sent either on this response.

It is deliberately not MemberExtra. A billable member is a user who counts against the seat total and not a membership record, so it carries no access level, no expiry and no role. The endpoint's own desc annotates Entities::Member while its handler presents this entity, which is why the audit reads nine membership keys against a response that has never carried one of them.

func CapturedBillableMembers

func CapturedBillableMembers(capture *gitlabclient.ResponseCapture, decoded int) ([]BillableMemberExtra, error)

CapturedBillableMembers reads them off the captured answer to a list of billable members, one extra per member in order, the count held to what the SDK decoded.

type BoardExtra

type BoardExtra struct {
	Group *BasicGroupDetailsOutput `json:"group"`
}

BoardExtra is that reference on the board, which GitLab exposes under no condition and leaves null on a board that belongs to a project.

func CapturedBoard

func CapturedBoard(capture *gitlabclient.ResponseCapture) (BoardExtra, error)

CapturedBoard reads it off the captured answer to a request for one board.

func CapturedBoards

func CapturedBoards(capture *gitlabclient.ResponseCapture, decoded int) ([]BoardExtra, error)

CapturedBoards reads the same off a list answer, one extra per board in order, the count held to what the SDK decoded.

type BoardLabelDetailsOutput

type BoardLabelDetailsOutput struct {
	ID          int64  `json:"id"`
	Name        string `json:"name"`
	Color       string `json:"color"`
	Description string `json:"description"`
}

BoardLabelDetailsOutput is the documented board `labels[]` entry. The documented update-board responses show only id, name, color, and description for each label; the SDK label types' remaining fields are not part of the documented board labels shape.

func NewBoardLabelDetailsOutputs

func NewBoardLabelDetailsOutputs(details []*gl.LabelDetails) []*BoardLabelDetailsOutput

NewBoardLabelDetailsOutputs converts a slice of gl.LabelDetails into the documented board label subset, skipping nil elements and returning nil when no labels remain (consistent with NewCustomAttributeOutputs, so callers never serialize an empty array for an effectively-empty input).

type BoardLabelOutput

type BoardLabelOutput struct {
	Name        string `json:"name"`
	Color       string `json:"color"`
	Description string `json:"description"`
}

BoardLabelOutput is the documented board-list label object. Every documented board-list response shows the list's `label` object with only name, color, and description; gl.Label's remaining fields are not part of the documented board-list label shape.

func NewBoardLabelOutput

func NewBoardLabelOutput(l *gl.Label) *BoardLabelOutput

NewBoardLabelOutput converts a gl.Label into the documented board-list label subset, returning nil when the SDK value is nil.

type BoardListAssigneeOutput

type BoardListAssigneeOutput struct {
	ID       int64  `json:"id"`
	Name     string `json:"name"`
	Username string `json:"username"`
}

BoardListAssigneeOutput is the compact assignee object on a board list (Premium/Ultimate assignee list type), matching gl.BoardListAssignee. The documented response examples cover only label and milestone lists, so this premium list-type sub-object has no fuller documented shape to trim against.

func NewBoardListAssigneeOutput

func NewBoardListAssigneeOutput(a *gl.BoardListAssignee) *BoardListAssigneeOutput

NewBoardListAssigneeOutput converts a gl.BoardListAssignee into the shared output shape, returning nil when the SDK value is nil.

type BoardListExtra

type BoardListExtra struct {
	LimitMetric string `json:"limit_metric"`
}

BoardListExtra is the metric a board list limits its work in progress by, which GitLab exposes only on a list whose board has work-in-progress limits available, so it is empty everywhere else.

func CapturedBoardList

func CapturedBoardList(capture *gitlabclient.ResponseCapture) (BoardListExtra, error)

CapturedBoardList reads it off the captured answer to a request for one board list.

func CapturedBoardLists

func CapturedBoardLists(capture *gitlabclient.ResponseCapture, decoded int) ([]BoardListExtra, error)

CapturedBoardLists reads the same off a list answer, one extra per board list in order, the count held to what the SDK decoded.

type BoardUserOutput

type BoardUserOutput struct {
	ID        int64  `json:"id"`
	Username  string `json:"username"`
	Name      string `json:"name"`
	State     string `json:"state"`
	AvatarURL string `json:"avatar_url"`
	WebURL    string `json:"web_url"`
}

BoardUserOutput is the documented 6-field board assignee object shared by the project boards and group boards APIs (doc/api/boards.md and doc/api/group_boards.md). The documented update-board responses show only id, name, username, state, avatar_url, and web_url; gl.BasicUser's created_at is not part of the documented board assignee shape.

func NewBoardUserOutput

func NewBoardUserOutput(u *gl.BasicUser) *BoardUserOutput

NewBoardUserOutput converts a gl.BasicUser into the documented board assignee subset, returning nil when the SDK value is nil.

type BridgeExtra

type BridgeExtra struct {
	Project *BridgeProjectOutput `json:"project"`
}

BridgeExtra is that object on a bridge job, exposed under no condition.

func CapturedBridges

func CapturedBridges(capture *gitlabclient.ResponseCapture, decoded int) ([]BridgeExtra, error)

CapturedBridges reads it off the captured answer to a list of bridges, one extra per bridge in order, the count held to what the SDK decoded.

type BridgeProjectOutput

type BridgeProjectOutput struct {
	CIJobTokenScopeEnabled bool `json:"ci_job_token_scope_enabled"`
}

BridgeProjectOutput is the project object GitLab renders on a job, which carries one key: whether the job token can reach outside this project.

type BroadcastMessageExtra

type BroadcastMessageExtra struct {
	Color string `json:"color"`
}

BroadcastMessageExtra is the color a broadcast message is drawn in, which GitLab's broadcast message entity exposes under no condition.

func CapturedBroadcastMessage

func CapturedBroadcastMessage(capture *gitlabclient.ResponseCapture) (BroadcastMessageExtra, error)

CapturedBroadcastMessage reads it off the captured answer to a request for one message.

func CapturedBroadcastMessages

func CapturedBroadcastMessages(capture *gitlabclient.ResponseCapture, decoded int) ([]BroadcastMessageExtra, error)

CapturedBroadcastMessages reads the same off a list answer, one extra per message in order, the count held to what the SDK decoded.

type CICDVariableFlags

type CICDVariableFlags struct {
	Protected bool
	Masked    bool
	Hidden    bool
	Raw       bool
}

CICDVariableFlags groups boolean CI/CD variable attributes for Markdown view-model construction.

type CICDVariableListMarkdownOptions

type CICDVariableListMarkdownOptions struct {
	Title                   string
	EmptyMessage            string
	IncludeEnvironmentScope bool
	Hints                   []string
}

CICDVariableListMarkdownOptions configures the shared CI/CD variable list renderer.

type CICDVariableMarkdown

type CICDVariableMarkdown struct {
	Key              string
	Value            string
	VariableType     string
	Protected        bool
	Masked           bool
	Hidden           bool
	Raw              bool
	EnvironmentScope string
	Description      string
}

CICDVariableMarkdown carries the common fields rendered by GitLab CI/CD variable tools at project, group, and instance scopes.

func CICDVariableMarkdowns

func CICDVariableMarkdowns[T any](variables []T, convert func(T) CICDVariableMarkdown) []CICDVariableMarkdown

CICDVariableMarkdowns maps package-specific variable outputs to the shared CI/CD variable Markdown view model.

func NewCICDVariableMarkdown

func NewCICDVariableMarkdown(key, value, variableType string, flags CICDVariableFlags, environmentScope, description string) CICDVariableMarkdown

NewCICDVariableMarkdown builds a shared Markdown view model for CI/CD variables without forcing tool packages to duplicate composite literals.

type CICDVariableMarkdownOptions

type CICDVariableMarkdownOptions struct {
	Title                   string
	IncludeEnvironmentScope bool
	Hints                   []string
}

CICDVariableMarkdownOptions configures the shared CI/CD variable detail renderer.

type CIVariableExtra

type CIVariableExtra struct {
	EnvironmentScope string `json:"environment_scope"`
	Hidden           bool   `json:"hidden"`
}

CIVariableExtra is what GitLab's CI variable entity sends that the instance variable struct does not carry: the environments the value applies to, and whether the value is hidden from every reader once set. Both are exposed only where the variable's own model answers to them.

func CapturedCIVariable

func CapturedCIVariable(capture *gitlabclient.ResponseCapture) (CIVariableExtra, error)

CapturedCIVariable reads them off the captured answer to a request for one variable.

func CapturedCIVariables

func CapturedCIVariables(capture *gitlabclient.ResponseCapture, decoded int) ([]CIVariableExtra, error)

CapturedCIVariables reads the same off a list answer, one extra per variable in order, the count held to what the SDK decoded.

type ClusterAgentExtra

type ClusterAgentExtra struct {
	IsReceptive bool `json:"is_receptive"`
}

ClusterAgentExtra is whether an agent is receptive, meaning GitLab connects out to it rather than waiting for it to connect in. The agent entity exposes it under no condition.

func CapturedClusterAgent

func CapturedClusterAgent(capture *gitlabclient.ResponseCapture) (ClusterAgentExtra, error)

CapturedClusterAgent reads it off the captured answer to a request for one agent.

func CapturedClusterAgents

func CapturedClusterAgents(capture *gitlabclient.ResponseCapture, decoded int) ([]ClusterAgentExtra, error)

CapturedClusterAgents reads the same off a list answer, one extra per agent in order, the count held to what the SDK decoded.

type CodedError

type CodedError struct {
	// contains filtered or unexported fields
}

CodedError carries a JSON-RPC code alongside the original error.

It exists because the go-sdk derives the wire code from the error value: its toWireError leaves Code at 0 unless the error is, or wraps, a *jsonrpc.Error. An unclassified error therefore reaches the client as code 0, which is not a JSON-RPC error code at all, and a client cannot tell "you sent the wrong arguments" from "the upstream is down" — the two failures it would act on differently.

Unwrap lists the cause before the code, so errors.As finds the innermost code first. That ordering is deliberate in both directions: a handler that wraps an argument failure in its own context still reports the argument code, and a cause that already carries a specific code (a not-found, say) keeps it rather than being flattened into the generic one.

func (*CodedError) Error

func (e *CodedError) Error() string

Error renders the cause alone: the code is for the transport, not the reader.

func (*CodedError) Unwrap

func (e *CodedError) Unwrap() []error

Unwrap exposes the cause first and the code second, in the order the type documentation explains.

type CommitCommentExtra

type CommitCommentExtra struct {
	CreatedAt *time.Time `json:"created_at"`
}

CommitCommentExtra is when a commit comment was written, which GitLab's commit note entity exposes under no condition.

func CapturedCommitComment

func CapturedCommitComment(capture *gitlabclient.ResponseCapture) (CommitCommentExtra, error)

CapturedCommitComment reads it off the captured answer to a request that returned one comment.

func CapturedCommitComments

func CapturedCommitComments(capture *gitlabclient.ResponseCapture, decoded int) ([]CommitCommentExtra, error)

CapturedCommitComments reads the same off a list answer, one extra per comment in order, the count held to what the SDK decoded.

type CommitOutput

type CommitOutput struct {
	ID             string   `json:"id"`
	ShortID        string   `json:"short_id"`
	Title          string   `json:"title"`
	AuthorName     string   `json:"author_name"`
	AuthorEmail    string   `json:"author_email"`
	AuthoredDate   string   `json:"authored_date,omitempty"`
	CommitterName  string   `json:"committer_name"`
	CommitterEmail string   `json:"committer_email"`
	CommittedDate  string   `json:"committed_date,omitempty"`
	CreatedAt      string   `json:"created_at,omitempty"`
	Message        string   `json:"message"`
	ParentIDs      []string `json:"parent_ids,omitempty"`
}

CommitOutput is the documented reference subset of the commit object surfaced on a release (per doc/api/releases/_index.md). The documented release commit JSON surfaces id, short_id, title, created_at, parent_ids, message, author_name, author_email, authored_date, committer_name, committer_email, committed_date. SDK-only commit fields (stats, status, project_id, web_url) are not part of the documented project-release commit subset and are intentionally omitted from this base type. The group-releases endpoint adds web_url; consumers that need it embed this type and add the extra field.

func NewCommitOutput

func NewCommitOutput(c gl.Commit) *CommitOutput

NewCommitOutput converts a gl.Commit into the canonical-key commit object, returning nil when the commit ID is empty (no commit associated with the release). Timestamps are formatted as RFC 3339 strings via FormatTimePtr.

type CompatibilityPolicy

type CompatibilityPolicy struct {
	ActionAliases    []ActionAliasSpec
	ParameterAliases []ParameterAliasSpec
}

CompatibilityPolicy carries compatibility aliases and their ownership policy.

func CloneCompatibilityPolicy

func CloneCompatibilityPolicy(policy CompatibilityPolicy) CompatibilityPolicy

CloneCompatibilityPolicy returns a defensive copy of compatibility metadata.

type CustomAttributeOutput

type CustomAttributeOutput struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

CustomAttributeOutput mirrors gl.CustomAttribute, a key/value custom attribute attached to a user.

func NewCustomAttributeOutputs

func NewCustomAttributeOutputs(attrs []*gl.CustomAttribute) []CustomAttributeOutput

NewCustomAttributeOutputs converts a []*gl.CustomAttribute slice, skipping nil elements and returning nil when no attributes remain.

type DeleteOutput

type DeleteOutput struct {
	HintableOutput
	Status  string `json:"status"`
	Message string `json:"message"`
}

DeleteOutput is a confirmation message returned by destructive tool handlers (delete, unprotect, unapprove) so the LLM receives explicit feedback instead of empty content when the operation succeeds.

func DeleteResult

func DeleteResult(resource string) (*mcp.CallToolResult, DeleteOutput, error)

DeleteResult builds a DeleteOutput and its Markdown representation for a successful destructive operation. The resource parameter describes what was affected (e.g., "project 42", "branch feature/x").

type DependencyExtra

type DependencyExtra struct {
	Malware *bool `json:"malware"`
}

DependencyExtra is whether a dependency is known malware, which GitLab exposes to a caller allowed to read the project's vulnerabilities and only while the instance has the feature enabled.

The pointer carries the third state: true is a detection, false is a package the scan cleared, and absent is a scan that did not run. Read into a bool the last two would both arrive as false.

func CapturedDependencies

func CapturedDependencies(capture *gitlabclient.ResponseCapture, decoded int) ([]DependencyExtra, error)

CapturedDependencies reads it off the captured answer to a list of dependencies, one extra per dependency in order, the count held to what the SDK decoded.

type DeployKeyExtra

type DeployKeyExtra struct {
	LastUsedAt                 *time.Time               `json:"last_used_at"`
	UsageType                  string                   `json:"usage_type"`
	ProjectsWithWriteAccess    []DeployKeyProjectOutput `json:"projects_with_write_access"`
	ProjectsWithReadonlyAccess []DeployKeyProjectOutput `json:"projects_with_readonly_access"`
}

DeployKeyExtra is what GitLab's deploy key entity sends that the key itself does not say: when the key was last used to reach the instance and what it may be used for, both unconditional, and the projects it can write to or only read from, which are sent when the request asks for them.

func CapturedDeployKey

func CapturedDeployKey(capture *gitlabclient.ResponseCapture) (DeployKeyExtra, error)

CapturedDeployKey reads them off the captured answer to a request for one deploy key.

func CapturedDeployKeys

func CapturedDeployKeys(capture *gitlabclient.ResponseCapture, decoded int) ([]DeployKeyExtra, error)

CapturedDeployKeys reads the same off a list answer, one extra per key in order, the count held to what the SDK decoded.

type DeployKeyProjectOutput

type DeployKeyProjectOutput struct {
	ID                int64      `json:"id"`
	Description       string     `json:"description"`
	Name              string     `json:"name"`
	NameWithNamespace string     `json:"name_with_namespace"`
	Path              string     `json:"path"`
	PathWithNamespace string     `json:"path_with_namespace"`
	CreatedAt         *time.Time `json:"created_at"`
}

DeployKeyProjectOutput is one project a deploy key reaches, rendered as the project identity entity: the naming and the creation date, without the settings a full project carries.

type DeploymentApprovalOutput

type DeploymentApprovalOutput struct {
	User      *UserBasicOutput `json:"user,omitempty"`
	Status    string           `json:"status"`
	CreatedAt *time.Time       `json:"created_at"`
	Comment   string           `json:"comment,omitempty"`
}

DeploymentApprovalOutput is one approval or rejection recorded against a deployment, as lib/api/entities/deployments/approval.rb renders it.

type DeploymentApprovalRuleOutput

type DeploymentApprovalRuleOutput struct {
	ID                     int64                      `json:"id"`
	UserID                 int64                      `json:"user_id,omitempty"`
	GroupID                int64                      `json:"group_id,omitempty"`
	AccessLevel            int64                      `json:"access_level,omitempty"`
	AccessLevelDescription string                     `json:"access_level_description,omitempty"`
	RequiredApprovals      int64                      `json:"required_approvals"`
	GroupInheritanceType   int64                      `json:"group_inheritance_type,omitempty"`
	DeploymentApprovals    []DeploymentApprovalOutput `json:"deployment_approvals,omitempty"`
}

DeploymentApprovalRuleOutput is one rule of the approval summary: who may approve, how many approvals it needs, and what has been recorded against it.

type DeploymentApprovalSummaryOutput

type DeploymentApprovalSummaryOutput struct {
	Rules []DeploymentApprovalRuleOutput `json:"rules,omitempty"`
}

DeploymentApprovalSummaryOutput is the rules a deployment must satisfy before it may run.

type DeploymentExtra

type DeploymentExtra struct {
	Approvals            []DeploymentApprovalOutput       `json:"approvals"`
	ApprovalSummary      *DeploymentApprovalSummaryOutput `json:"approval_summary"`
	PendingApprovalCount int64                            `json:"pending_approval_count"`
}

DeploymentExtra is what GitLab's extended deployment entity sends that client-go's Deployment does not carry: the approvals recorded so far, how many are still outstanding, and the rules they are counted against. All three are exposed under no condition, so a deployment that needs no approval carries them empty rather than not at all.

func CapturedDeployment

func CapturedDeployment(capture *gitlabclient.ResponseCapture) (DeploymentExtra, error)

CapturedDeployment reads them off the captured answer to a request for one deployment.

func CapturedDeployments

func CapturedDeployments(capture *gitlabclient.ResponseCapture, decoded int) ([]DeploymentExtra, error)

CapturedDeployments reads the same off a list answer, one extra per deployment in order, the count held to what the SDK decoded.

type DetailedError

type DetailedError struct {
	Domain       string `json:"domain"`
	Action       string `json:"action"`
	Message      string `json:"message"`
	Details      string `json:"details,omitempty"`
	GitLabStatus int    `json:"gitlab_status,omitempty"`
	RequestID    string `json:"request_id,omitempty"`
}

DetailedError represents a rich, structured error with domain context for diagnostic output. It extends ToolError with additional fields useful for automated issue creation and Markdown error reporting.

func NewDetailedError

func NewDetailedError(domain, action string, err error) *DetailedError

NewDetailedError creates a DetailedError from a GitLab API error, extracting HTTP status and request ID when available.

func (*DetailedError) Error

func (e *DetailedError) Error() string

Error returns a concise representation: "domain/action: message".

func (*DetailedError) Markdown

func (e *DetailedError) Markdown() string

Markdown renders the error as a Markdown block suitable for display in MCP tool results. Includes all available context for diagnostics.

type DiffLine

type DiffLine struct {
	OldLine int      // Line number in old file (0 for added lines)
	NewLine int      // Line number in new file (0 for removed lines)
	Type    LineType // Whether the line was added, removed, or unchanged
}

DiffLine represents a single line in a parsed unified diff with its old/new line numbers and type (added, removed, or context).

func ParseDiffLines

func ParseDiffLines(diff string) []DiffLine

ParseDiffLines parses a unified diff string and returns metadata for each line, including old/new line numbers and whether it is added, removed, or context. Only lines inside @@ hunk headers are returned.

type DiffOutput

type DiffOutput struct {
	OldPath     string `json:"old_path"`
	NewPath     string `json:"new_path"`
	AMode       string `json:"a_mode,omitempty"`
	BMode       string `json:"b_mode,omitempty"`
	Diff        string `json:"diff"`
	NewFile     bool   `json:"new_file"`
	RenamedFile bool   `json:"renamed_file"`
	DeletedFile bool   `json:"deleted_file"`
}

DiffOutput represents a single file diff from the GitLab API. It is used by both commit diff and repository compare operations.

func DiffToOutput

func DiffToOutput(d *gl.Diff) DiffOutput

DiffToOutput converts a GitLab API gl.Diff to the MCP tool output format.

type DiffRefsOutput

type DiffRefsOutput struct {
	BaseSHA  string `json:"base_sha"`
	HeadSHA  string `json:"head_sha"`
	StartSHA string `json:"start_sha"`
}

DiffRefsOutput mirrors the diff_refs object on a merge request, carrying the base, head, and start SHAs of the diff. It is byte-identical across the mergerequests and deploymentmergerequests domains; the canonical copy lives here so both packages can alias it without introducing import cycles (ADR-0004).

type DiscussionExtra

type DiscussionExtra struct {
	Resolvable bool        `json:"resolvable"`
	Resolved   bool        `json:"resolved"`
	Notes      []NoteExtra `json:"notes"`
}

DiscussionExtra is what GitLab's Discussion entity sends that client-go's Discussion does not model, the thread's own resolution state, read from the captured response beside the notes' own extras (see CapturedDiscussion).

func CapturedDiscussion

func CapturedDiscussion(capture *gitlabclient.ResponseCapture) (DiscussionExtra, error)

CapturedDiscussion reads, from the captured answer of a call that returned one discussion, what GitLab sends that client-go's Discussion and its notes do not model.

func CapturedDiscussions

func CapturedDiscussions(capture *gitlabclient.ResponseCapture, decoded int) ([]DiscussionExtra, error)

CapturedDiscussions reads the same from the captured answer of a call that returned a list of discussions, one extra per discussion in the list's order, the count held to what the SDK decoded as CapturedNotes holds it.

type DiscussionListMarkdownOptions

type DiscussionListMarkdownOptions struct {
	Title             string
	EmptyMessage      string
	Pagination        PaginationOutput
	GraphQLPagination *GraphQLPaginationOutput
	Hints             []string
}

DiscussionListMarkdownOptions configures shared discussion list rendering.

type DiscussionMarkdown

type DiscussionMarkdown struct {
	ID    string
	Notes []DiscussionNoteMarkdown
}

DiscussionMarkdown carries common discussion fields for Markdown responses.

func DiscussionMarkdowns

func DiscussionMarkdowns[T any](discussions []T, convert func(T) DiscussionMarkdown) []DiscussionMarkdown

DiscussionMarkdowns maps package-specific discussion outputs to the shared discussion Markdown view model.

func DiscussionThreadOutputMarkdowns

func DiscussionThreadOutputMarkdowns(discussions []DiscussionThreadOutput) []DiscussionMarkdown

DiscussionThreadOutputMarkdowns maps thread outputs to Markdown view models.

func NewDiscussionMarkdown

func NewDiscussionMarkdown(id string, notes []DiscussionNoteMarkdown) DiscussionMarkdown

NewDiscussionMarkdown builds a shared Markdown view model for discussion threads.

type DiscussionNoteMarkdown

type DiscussionNoteMarkdown struct {
	ID        int64
	Body      string
	Author    string
	CreatedAt string
}

DiscussionNoteMarkdown carries common note fields rendered inside discussion Markdown responses.

func DiscussionNoteMarkdowns

func DiscussionNoteMarkdowns[T any](notes []T, convert func(T) DiscussionNoteMarkdown) []DiscussionNoteMarkdown

DiscussionNoteMarkdowns maps package-specific note outputs to the shared discussion note Markdown view model.

func NewDiscussionNoteMarkdown

func NewDiscussionNoteMarkdown(id int64, body, author, createdAt string) DiscussionNoteMarkdown

NewDiscussionNoteMarkdown builds a shared Markdown view model for discussion notes.

type DiscussionRenderer

type DiscussionRenderer struct {
	ListTitle       string
	EmptyMessage    string
	ListHints       []string
	DiscussionHints []string
	NoteHints       []string
}

DiscussionRenderer stores stable labels and hints for a discussion family so package formatters can avoid repeating identical rendering glue.

func NewDiscussionRenderer

func NewDiscussionRenderer(listTitle, emptyMessage, listHint, discussionHint, noteHint string) DiscussionRenderer

NewDiscussionRenderer builds a renderer for discussion tool families that use one hint for each list, discussion, and note view.

func (DiscussionRenderer) FormatDiscussion

func (r DiscussionRenderer) FormatDiscussion(discussion DiscussionMarkdown) string

FormatDiscussion renders a single discussion using the renderer hints.

func (DiscussionRenderer) FormatGraphQLForwardList

func (r DiscussionRenderer) FormatGraphQLForwardList(discussions []DiscussionMarkdown, pagination GraphQLForwardPaginationOutput) string

FormatGraphQLForwardList renders GraphQL discussion threads from a connection that only pages forward, so the summary line names no previous page.

func (DiscussionRenderer) FormatGraphQLList

func (r DiscussionRenderer) FormatGraphQLList(discussions []DiscussionMarkdown, pagination GraphQLPaginationOutput) string

FormatGraphQLList renders GraphQL discussion threads with cursor pagination.

func (DiscussionRenderer) FormatNote

FormatNote renders a single discussion note using the renderer hints.

func (DiscussionRenderer) FormatRESTList

func (r DiscussionRenderer) FormatRESTList(discussions []DiscussionMarkdown, pagination PaginationOutput) string

FormatRESTList renders REST discussion threads with offset pagination.

type DiscussionThreadNoteOutput

type DiscussionThreadNoteOutput struct {
	HintableOutput
	ID              int64               `json:"id"`
	Body            string              `json:"body"`
	Author          *NoteUserOutput     `json:"author,omitempty"`
	CreatedAt       string              `json:"created_at"`
	UpdatedAt       string              `json:"updated_at,omitempty"`
	Resolved        bool                `json:"resolved"`
	Resolvable      bool                `json:"resolvable"`
	ResolvedAt      string              `json:"resolved_at,omitempty"`
	ResolvedBy      *NoteUserOutput     `json:"resolved_by,omitempty"`
	System          bool                `json:"system"`
	Internal        bool                `json:"internal"`
	Confidential    bool                `json:"confidential"`
	Type            string              `json:"type,omitempty"`
	NoteableType    string              `json:"noteable_type,omitempty"`
	NoteableID      int64               `json:"noteable_id,omitempty"`
	NoteableIID     int64               `json:"noteable_iid,omitempty"`
	CommitID        string              `json:"commit_id,omitempty"`
	Position        *NotePositionOutput `json:"position,omitempty"`
	ProjectID       int64               `json:"project_id,omitempty"`
	Imported        bool                `json:"imported"`
	ImportedFrom    string              `json:"imported_from,omitempty"`
	CommandsChanges map[string]any      `json:"commands_changes,omitempty"`
	Suggestions     []SuggestionOutput  `json:"suggestions,omitempty"`
}

DiscussionThreadNoteOutput mirrors every field of GitLab's Note entity as it appears within a REST discussion thread (mrdiscussions, commitdiscussions, issuediscussions, snippetdiscussions). The shape differs from NoteOutput in omitempty semantics: UpdatedAt carries omitempty (absent on notes created without an update), while Resolved and Resolvable are always emitted (never omitted) because thread resolution state is always meaningful. Field order and JSON tags are locked.

func CapturedThreadNote

func CapturedThreadNote(op string, n *gl.Note, capture *gitlabclient.ResponseCapture) (DiscussionThreadNoteOutput, error)

CapturedThreadNote converts one note of a thread the same way.

func DiscussionThreadNoteOutputFromGitLab

func DiscussionThreadNoteOutputFromGitLab(n *gl.Note, extra NoteExtra) DiscussionThreadNoteOutput

DiscussionThreadNoteOutputFromGitLab converts a gl.Note within a discussion thread, and what the captured response adds to it, into the thread note shape. A nil note converts to the zero value, since the SDK returns []*gl.Note and an element may be nil.

func (DiscussionThreadNoteOutput) MarkdownNote

MarkdownNote returns the shared Markdown view model for a thread note, naming the author by username.

type DiscussionThreadOutput

type DiscussionThreadOutput struct {
	HintableOutput
	ID             string                        `json:"id"`
	IndividualNote bool                          `json:"individual_note"`
	Resolvable     bool                          `json:"resolvable"`
	Resolved       bool                          `json:"resolved,omitempty"`
	Notes          []*DiscussionThreadNoteOutput `json:"notes"`
}

DiscussionThreadOutput mirrors GitLab's Discussion entity with its full note payloads as returned by the REST Discussions API. Notes are pointer elements because the SDK returns []*gl.Note and individual notes may be nil in edge cases. `resolvable` and `resolved` are the thread's own, which client-go does not model; `resolved` is sent only for a resolvable thread.

func CapturedThread

func CapturedThread(op string, d *gl.Discussion, capture *gitlabclient.ResponseCapture) (DiscussionThreadOutput, error)

CapturedThread converts one discussion with what its captured answer carries beside the SDK's decode, or reports under op the answer the type cannot hold. It is the whole tail of a handler that returns one thread.

func CapturedThreads

func CapturedThreads(op string, ds []*gl.Discussion, capture *gitlabclient.ResponseCapture) ([]DiscussionThreadOutput, error)

CapturedThreads does the same for a list of discussions, pairing each with its own extra by position.

func DiscussionThreadOutputFromGitLab

func DiscussionThreadOutputFromGitLab(d *gl.Discussion, extra DiscussionExtra) DiscussionThreadOutput

DiscussionThreadOutputFromGitLab converts a gl.Discussion and what the captured response adds to it into the thread shape, every note included. A note the capture holds no extra for, which cannot happen for an answer the SDK decoded, converts with none; a nil discussion converts to the zero value.

func DiscussionThreadOutputsFromGitLab

func DiscussionThreadOutputsFromGitLab(discussions []*gl.Discussion, extras []DiscussionExtra) []DiscussionThreadOutput

DiscussionThreadOutputsFromGitLab converts a list of discussions with the extras the captured list answer holds for each, by position.

func (DiscussionThreadOutput) MarkdownDiscussion

func (d DiscussionThreadOutput) MarkdownDiscussion() DiscussionMarkdown

MarkdownDiscussion returns the shared Markdown view model for a thread.

type EpicOutput

type EpicOutput struct {
	ID                      int64            `json:"id"`
	IID                     int64            `json:"iid"`
	GroupID                 int64            `json:"group_id"`
	ParentID                int64            `json:"parent_id"`
	Title                   string           `json:"title"`
	Description             string           `json:"description"`
	State                   string           `json:"state"`
	Confidential            bool             `json:"confidential"`
	WebURL                  string           `json:"web_url"`
	URL                     string           `json:"url"`
	Author                  *IssueUserOutput `json:"author,omitempty"`
	Labels                  []string         `json:"labels,omitempty"`
	Upvotes                 int64            `json:"upvotes,omitempty"`
	Downvotes               int64            `json:"downvotes,omitempty"`
	UserNotesCount          int64            `json:"user_notes_count,omitempty"`
	StartDate               string           `json:"start_date,omitempty"`
	StartDateIsFixed        bool             `json:"start_date_is_fixed,omitempty"`
	StartDateFixed          string           `json:"start_date_fixed,omitempty"`
	StartDateFromMilestones string           `json:"start_date_from_milestones,omitempty"`
	DueDate                 string           `json:"due_date,omitempty"`
	DueDateIsFixed          bool             `json:"due_date_is_fixed,omitempty"`
	DueDateFixed            string           `json:"due_date_fixed,omitempty"`
	DueDateFromMilestones   string           `json:"due_date_from_milestones,omitempty"`
	CreatedAt               string           `json:"created_at,omitempty"`
	UpdatedAt               string           `json:"updated_at,omitempty"`
	ClosedAt                string           `json:"closed_at,omitempty"`
}

EpicOutput mirrors gl.Epic (the nested epic object surfaced on issue.epic). The nested EpicAuthorOutput is the same IssueUserOutput shape used elsewhere in the issues package.

func NewEpicOutput

func NewEpicOutput(e *gl.Epic) *EpicOutput

NewEpicOutput converts a gl.Epic pointer into the canonical epic object, returning nil for a nil source.

type EventExtra

type EventExtra struct {
	Imported     bool                 `json:"imported"`
	ImportedFrom string               `json:"imported_from"`
	WikiPage     *EventWikiPageOutput `json:"wiki_page"`
}

EventExtra is what GitLab's event entity sends that the event itself does not say: whether the event arrived with an import rather than happening here and which platform it came from, both unconditional, and the wiki page an event about a wiki names.

func CapturedEvents

func CapturedEvents(capture *gitlabclient.ResponseCapture, decoded int) ([]EventExtra, error)

CapturedEvents reads them off the captured answer to a list of events, one extra per event in order, the count held to what the SDK decoded.

type EventWikiPageOutput

type EventWikiPageOutput struct {
	Format         string `json:"format"`
	Slug           string `json:"slug"`
	Title          string `json:"title"`
	WikiPageMetaID int64  `json:"wiki_page_meta_id"`
}

EventWikiPageOutput is the wiki page an event happened to, as the basic wiki page entity renders it: how the page is written, where it lives, its title, and the identifier of the record that survives a rename.

type EvidenceOutput

type EvidenceOutput struct {
	SHA         string `json:"sha,omitempty"`
	Filepath    string `json:"filepath,omitempty"`
	CollectedAt string `json:"collected_at,omitempty"`
}

EvidenceOutput mirrors gl.ReleaseEvidence (the per-evidence object surfaced on the release's evidences array).

func NewEvidenceOutputs

func NewEvidenceOutputs(evs []*gl.ReleaseEvidence) []*EvidenceOutput

NewEvidenceOutputs converts a slice of gl.ReleaseEvidence, skipping nil elements and returning nil for an empty input.

type FeatureDefinitionExtra

type FeatureDefinitionExtra struct {
	FeatureIssueURL     string `json:"feature_issue_url"`
	IntendedToRolloutBy string `json:"intended_to_rollout_by"`
}

FeatureDefinitionExtra is where a feature flag's definition points a reader: the issue that tracks it and the milestone it is meant to roll out by. The definition entity exposes both under no condition.

func CapturedFeatureDefinitions

func CapturedFeatureDefinitions(capture *gitlabclient.ResponseCapture, decoded int) ([]FeatureDefinitionExtra, error)

CapturedFeatureDefinitions reads them off the captured answer to a list of definitions, one extra per definition in order, the count held to what the SDK decoded.

type FeatureExtra

type FeatureExtra struct {
	Definition FeatureDefinitionExtra `json:"definition"`
}

FeatureExtra reaches the same two keys where a feature carries its definition under a key of its own, which is the shape the feature list answers with.

func CapturedFeature

func CapturedFeature(capture *gitlabclient.ResponseCapture) (FeatureExtra, error)

CapturedFeature reads them off the captured answer to a request that set one feature flag.

func CapturedFeatures

func CapturedFeatures(capture *gitlabclient.ResponseCapture, decoded int) ([]FeatureExtra, error)

CapturedFeatures reads them off the captured answer to a list of features, one extra per feature in order, the count held to what the SDK decoded.

type FeatureFlagUserListExtra

type FeatureFlagUserListExtra struct {
	Path     string `json:"path"`
	EditPath string `json:"edit_path"`
}

FeatureFlagUserListExtra is where a user list lives in GitLab's own web interface, both exposed under no condition.

func CapturedFeatureFlagUserList

func CapturedFeatureFlagUserList(capture *gitlabclient.ResponseCapture) (FeatureFlagUserListExtra, error)

CapturedFeatureFlagUserList reads them off the captured answer to a request for one user list.

func CapturedFeatureFlagUserLists

func CapturedFeatureFlagUserLists(capture *gitlabclient.ResponseCapture, decoded int) ([]FeatureFlagUserListExtra, error)

CapturedFeatureFlagUserLists reads the same off a list answer, one extra per user list in order, the count held to what the SDK decoded.

type FormatResultFunc

type FormatResultFunc func(any) *mcp.CallToolResult

FormatResultFunc converts an action result into an MCP call tool result.

type GraphQLCursor

type GraphQLCursor struct {
	First  *int
	After  string
	Last   *int
	Before string
}

GraphQLCursor is one resolved cursor request. At most one of First and Last is set, which is what makes the direction the caller asked for the direction they get.

type GraphQLCursorPaginationInput

type GraphQLCursorPaginationInput struct {
	GraphQLPaginationInput
	Last   *int   `` /* 141-byte string literal not displayed */
	Before string `` /* 172-byte string literal not displayed */
}

GraphQLCursorPaginationInput adds backward pagination to GraphQLPaginationInput, for the connections GitLab lets a caller walk in both directions. Embed it only when the operation declares all four variables: GraphQLCursorPaginationInput.Variables refuses a document that does not.

func (GraphQLCursorPaginationInput) Resolve

Resolve turns the four requested parameters into the pair GitLab accepts.

The cursor picks the direction and the count only sizes the page. A before cursor sent beside first is not backward pagination on any GitLab connection: the keyset ones answer it with the head of the list and the array-backed ones intersect first with last, so a caller following start_cursor back through a list would loop on the page they were already on. Sending both counts is refused rather than reinterpreted, because GitLab refuses it too and there is no reading of the pair that is not a guess.

func (GraphQLCursorPaginationInput) Variables

func (p GraphQLCursorPaginationInput) Variables(document string) (map[string]any, error)

Variables returns the variable map for document, refusing a document that declares fewer variables than this input can send. See GraphQLPaginationInput.Variables for why the document is a parameter.

type GraphQLError

type GraphQLError struct {
	Message string `json:"message"`
}

GraphQLError is one top-level GraphQL error returned in a successful HTTP response body.

type GraphQLForwardPaginationOutput

type GraphQLForwardPaginationOutput struct {
	HasNextPage bool   `json:"has_next_page"`
	EndCursor   string `json:"end_cursor,omitempty"`
}

GraphQLForwardPaginationOutput is the pagination metadata of a connection that only pages forward: whether another page follows, and the cursor that asks for it.

It exists so that a tool taking GraphQLPaginationInput cannot report a previous page. GitLab's keyset connections fill in hasPreviousPage and startCursor from their second page on whether or not the field accepts before, so a forward-only tool reporting the whole of pageInfo hands a model a cursor and no parameter to spend it on, which reads as a capability the tool withdrew rather than as the dead end it is.

func ForwardPageInfoToOutput

ForwardPageInfoToOutput converts the forward half of pageInfo, as a forward-only document selects it, to the forward-only output struct.

type GraphQLNoteMutation

type GraphQLNoteMutation struct {
	// Op is the operation name used in wrapped errors (e.g. "epicNoteCreate").
	Op string
	// Hint is the corrective hint attached to transport errors.
	Hint string
	// PayloadKey is the mutation payload key in the response data object
	// ("createNote" or "updateNote").
	PayloadKey string
	// Query is the GraphQL mutation document.
	Query string
	// Variables holds the mutation variables.
	Variables map[string]any
}

GraphQLNoteMutation describes one work item note mutation call (createNote/updateNote) so the shared executor can apply the error conventions used by every GraphQL note domain (epic notes, epic discussions): transport errors are wrapped with the operation name and corrective hint, the first mutation payload error becomes "op: message", and a missing note node becomes "op: no note returned".

type GraphQLPageInfo

type GraphQLPageInfo struct {
	HasNextPage     bool   `json:"has_next_page"`
	HasPreviousPage bool   `json:"has_previous_page"`
	EndCursor       string `json:"end_cursor,omitempty"`
	StartCursor     string `json:"start_cursor,omitempty"`
}

GraphQLPageInfo holds cursor-based pagination metadata returned by GraphQL connection responses. It maps directly to GitLab's PageInfo type.

type GraphQLPaginationInput

type GraphQLPaginationInput struct {
	First *int   `json:"first,omitempty" jsonschema:"Number of items to return (default 20, max 100)"`
	After string `json:"after,omitempty" jsonschema:"Cursor for forward pagination (from previous response end_cursor)"`
}

GraphQLPaginationInput holds forward cursor pagination parameters for a GraphQL list query: first and after, and nothing else.

Forward only is the safe default rather than a limitation of the helper. A connection paginates backwards only where GitLab's own field accepts before and last, and several fields this server queries refuse them: Project's branchRules and the work item notes widget's discussions both answer those arguments with argumentNotAccepted. What the two do with the backward half of pageInfo differs, which is why the promise is withdrawn in the output as well as in the input: branchRules reports no previous page and no start cursor, while the discussions widget is keyset-paginated and reports both from its second page on, offering a cursor no argument could ever spend. A domain whose field does accept the pair embeds GraphQLCursorPaginationInput, which is the only type that can put it on the wire, and reports the whole of pageInfo through GraphQLPaginationOutput; a forward-only domain reports GraphQLForwardPaginationOutput instead.

func (GraphQLPaginationInput) EffectiveFirst

func (p GraphQLPaginationInput) EffectiveFirst() int

EffectiveFirst returns the requested page size, clamped to [1, GraphQLMaxFirst] with GraphQLDefaultFirst as fallback.

func (GraphQLPaginationInput) Variables

func (p GraphQLPaginationInput) Variables(document string) (map[string]any, error)

Variables returns the variable map for document, which must be the operation the caller is about to execute.

The document is a parameter because a variable an operation does not declare is ignored rather than rejected: a page request carrying one is answered with somebody else's page and no error. Taking the document here is what makes that impossible, since the only way to obtain the map is to name the operation it is going to.

type GraphQLPaginationOutput

type GraphQLPaginationOutput struct {
	HasNextPage     bool   `json:"has_next_page"`
	HasPreviousPage bool   `json:"has_previous_page"`
	EndCursor       string `json:"end_cursor,omitempty"`
	StartCursor     string `json:"start_cursor,omitempty"`
}

GraphQLPaginationOutput holds pagination metadata for GraphQL list tool responses, presented in a consistent format for LLM consumers.

func PageInfoToOutput

func PageInfoToOutput(pi GraphQLRawPageInfo) GraphQLPaginationOutput

PageInfoToOutput converts a raw GraphQL PageInfo response struct (with camelCase JSON keys from the API) to the snake_case output struct.

type GraphQLRawForwardPageInfo

type GraphQLRawForwardPageInfo struct {
	HasNextPage bool   `json:"hasNextPage"`
	EndCursor   string `json:"endCursor"`
}

GraphQLRawForwardPageInfo is the half of pageInfo a forward-only document selects. A forward-only tool decodes into this rather than into GraphQLRawPageInfo, so its decoder declares exactly what its document asks for: a decoder carrying the backward half beside a document that never selects it holds two fields that are always empty, which is what make check-graphql-shapes refuses.

type GraphQLRawPageInfo

type GraphQLRawPageInfo struct {
	HasNextPage     bool   `json:"hasNextPage"`
	HasPreviousPage bool   `json:"hasPreviousPage"`
	EndCursor       string `json:"endCursor"`
	StartCursor     string `json:"startCursor"`
}

GraphQLRawPageInfo matches the camelCase JSON shape returned by the GitLab GraphQL API before conversion to our snake_case output.

type HintSetter

type HintSetter interface {
	// SetNextSteps stores the extracted next-step hints on the output struct.
	SetNextSteps(hints []string)
}

HintSetter is implemented by any Output struct that embeds HintableOutput. PopulateHints uses this interface to set extracted hints on the output.

type HintableOutput

type HintableOutput struct {
	NextSteps []string `json:"next_steps,omitempty"`
}

HintableOutput is an embeddable struct that adds a next_steps field to any Output type. Embed it as the FIRST field of an Output struct so that next_steps appears first in the serialized JSON, giving LLMs immediate guidance before reading the rest of the payload.

type Output struct {
    toolutil.HintableOutput
    Name string `json:"name"`
}

func (*HintableOutput) SetNextSteps

func (h *HintableOutput) SetNextSteps(hints []string)

SetNextSteps stores the given hints in the NextSteps field.

type HookHeaderOutput

type HookHeaderOutput struct {
	Key string `json:"key"`
}

HookHeaderOutput is one custom header a webhook sends with every delivery. Only the name is read: GitLab masks the value on the way out, and a header value is secret-bearing.

type ImpersonationTokenExtra

type ImpersonationTokenExtra struct {
	TokenExtra
	Impersonation bool   `json:"impersonation"`
	Description   string `json:"description"`
	UserID        int64  `json:"user_id"`
}

ImpersonationTokenExtra is what lib/api/entities/impersonation_token.rb sends beyond TokenExtra that client-go's ImpersonationToken does not carry. Its impersonation flag is its own; description and user_id come from the personal access token entity it inherits, which the SDK models on PersonalAccessToken and not on ImpersonationToken.

func CapturedImpersonationToken

func CapturedImpersonationToken(capture *gitlabclient.ResponseCapture) (ImpersonationTokenExtra, error)

CapturedImpersonationToken reads, off the captured answer to a request for one impersonation token, the fields client-go's ImpersonationToken does not model.

func CapturedImpersonationTokens

func CapturedImpersonationTokens(capture *gitlabclient.ResponseCapture, decoded int) ([]ImpersonationTokenExtra, error)

CapturedImpersonationTokens reads the same off a list answer, one extra per token in order, the count held to what the SDK decoded.

type IndividualToolAnnotationOverrides

type IndividualToolAnnotationOverrides struct {
	ReadOnly    *bool
	Destructive *bool
	Idempotent  *bool
	OpenWorld   *bool
}

IndividualToolAnnotationOverrides carries compatibility overrides for historical individual-tool annotations that intentionally differ from the canonical action semantics.

func (IndividualToolAnnotationOverrides) NarrowingOnly

func (o IndividualToolAnnotationOverrides) NarrowingOnly(readOnly, idempotent bool) IndividualToolAnnotationOverrides

NarrowingOnly returns the overrides with every claim removed that would make a mutating or non-repeatable action look safer than the action itself is.

readOnlyHint and idempotentHint both say "this call is safe to make, and safe to repeat", and both are consulted by things that act on them: --read-only removes what is not read-only, safe mode previews what is not, and a gateway may auto-allow readOnlyHint:true without asking anyone. An override that raises either one is therefore not a presentation choice, it is a silent widening of the operator's own controls.

Overrides that narrow are untouched, and are the reason this type exists: a delete whose confirmation is handled elsewhere may declare destructiveHint false, and an update that is not repeatable may declare idempotentHint false.

The case this was written for is system_hook_test, a mutating create that declared readOnlyHint true on the individual surface alone because the test event changes nothing on the instance. It changes no GitLab state, but it makes GitLab deliver an event to the hook's configured URL, so it is not a read — and one action classified read-only on one surface and mutating on the other two cannot be right on more than one of them.

type IndividualToolProjectionOptions

type IndividualToolProjectionOptions struct {
	Description string
	Icons       []mcp.Icon
}

IndividualToolProjectionOptions contains surface-level metadata that is shared by an individual tool projection but not owned by the action spec itself.

type IndividualToolSpec

type IndividualToolSpec struct {
	Name                string
	Title               string
	Description         string
	AnnotationOverrides IndividualToolAnnotationOverrides
}

IndividualToolSpec carries compatibility metadata for the individual-tool surface.

func CloneIndividualToolSpec

func CloneIndividualToolSpec(spec IndividualToolSpec) IndividualToolSpec

CloneIndividualToolSpec returns a defensive copy of individual-tool metadata.

type InputSchemaOverride

type InputSchemaOverride struct {
	PropertyPath string
	Values       map[string]any
}

InputSchemaOverride describes a deterministic JSON Schema patch for an action input schema. PropertyPath is a dot-separated input property path; an empty path applies Values at the schema root. Array properties automatically traverse through their items schema for nested paths.

func FilterOverridesForSchema

func FilterOverridesForSchema(schema map[string]any, overrides []InputSchemaOverride) []InputSchemaOverride

FilterOverridesForSchema returns the subset of overrides whose property path still resolves against schema (an empty path targets the root and is always kept). It is used after tier pruning removes higher-tier properties: an override that targeted a now-removed field (e.g. an ultimate-only parameter pruned for a Free instance) is dropped so it does not fail [validateInputSchemaOverrides] during catalog assembly. The enum/patch values were already applied to the property before pruning, so dropping the override here only keeps the override list consistent with the pruned schema.

func SchemaAnyOfRequired

func SchemaAnyOfRequired(propertyNames ...string) InputSchemaOverride

SchemaAnyOfRequired returns a root override that requires at least one of the supplied property names to be present.

func SchemaApproverIDsOverride

func SchemaApproverIDsOverride(propertyPath string) InputSchemaOverride

SchemaApproverIDsOverride widens the array item type of an ApproverIDsFilter parameter so both numeric user IDs and the "Any"/"None" literals pass input validation. Without it the reflected schema advertises strings only, which would reject the numeric IDs every existing caller sends.

func SchemaEnumOverride

func SchemaEnumOverride(propertyPath string, values ...string) InputSchemaOverride

SchemaEnumOverride returns an input-schema override that constrains the string parameter at propertyPath to a fixed set of enum values. It collapses the repeated `SchemaPropertyOverride(path, map[string]any{"enum": []any{...}})` block used across domains into a single call so the fixed-vocabulary constraint is expressed once per action without structural duplication.

func SchemaFormatOverride

func SchemaFormatOverride(propertyPath, format string) InputSchemaOverride

SchemaFormatOverride returns an input-schema override that sets the JSON Schema `format` of the string parameter at propertyPath (e.g. "date" for a YYYY-MM-DD field or "uri" for a URL field).

func SchemaPropertyOverride

func SchemaPropertyOverride(propertyPath string, values map[string]any) InputSchemaOverride

SchemaPropertyOverride returns an input-schema override for a property path.

func SchemaRootOverride

func SchemaRootOverride(values map[string]any) InputSchemaOverride

SchemaRootOverride returns an input-schema override applied at the schema root.

type InstanceUserExtra

type InstanceUserExtra struct {
	UserExtra
	BioHTML                     string     `json:"bio_html"`
	EnterpriseGroupID           *int64     `json:"enterprise_group_id"`
	EnterpriseGroupAssociatedAt *time.Time `json:"enterprise_group_associated_at"`
	ProvisionedByGroupID        *int64     `json:"provisioned_by_group_id"`
	UnconfirmedEmail            string     `json:"unconfirmed_email"`
}

InstanceUserExtra is UserExtra plus the five keys only the instance-wide user routes ever send, which is what separates internal/tools/users from the three group-scoped packages sharing the smaller shape: those serve GET /groups/:id/{enterprise_users,provisioned_users,saml_users}, and GitLab presents every one of them with UserPublic.

bio_html comes from lib/api/entities/users/bio_html.rb, which only UserProfile includes, so of the routes here it is on GET /users/:id alone. The three license-gated keys come from ee/lib/ee/api/entities/user_with_admin.rb, which only POST /users and PUT /users/:id present. unconfirmed_email is not a user key at all: lib/api/entities/service_account.rb sends it, on the six-key object POST /service_accounts answers with, when the account has an address change waiting to be confirmed.

The two identifiers are pointers for the reason the counts are: a license that does not carry the feature sends no key, and group 0 is not that.

func CapturedInstanceUser

func CapturedInstanceUser(capture *gitlabclient.ResponseCapture) (InstanceUserExtra, error)

CapturedInstanceUser reads, off the captured answer to a request for one user on an instance-wide route, everything CapturedUser reads and the five keys beside it.

func CapturedInstanceUsers

func CapturedInstanceUsers(capture *gitlabclient.ResponseCapture, decoded int) ([]InstanceUserExtra, error)

CapturedInstanceUsers reads the same off a list answer, one extra per user in order, the count held to what the SDK decoded.

type InvitationExtra

type InvitationExtra struct {
	InviteToken string `json:"invite_token"`
}

InvitationExtra is the token lib/api/entities/invitation.rb exposes under no condition and client-go's PendingInvite does not model. It is what the invitation URL a recipient follows is built from, so a caller allowed to list a group's pending invitations can reissue one without the mail.

func CapturedPendingInvites

func CapturedPendingInvites(capture *gitlabclient.ResponseCapture, decoded int) ([]InvitationExtra, error)

CapturedPendingInvites reads it off the captured answer to a list of pending invitations, one extra per invitation in order, the count held to what the SDK decoded.

type IssueLinksOutput

type IssueLinksOutput struct {
	Self       string `json:"self"`
	Notes      string `json:"notes"`
	AwardEmoji string `json:"award_emoji"`
	Project    string `json:"project"`
}

IssueLinksOutput mirrors gl.Links (the _links object on an issue). Field set is intentionally smaller than the release _links object in toolutil.LinksOutput — issues expose only self / notes / award_emoji / project URLs.

func NewIssueLinksOutput

func NewIssueLinksOutput(l *gl.IssueLinks) *IssueLinksOutput

NewIssueLinksOutput converts a gl.IssueLinks value into the canonical issue-links object, returning nil for a nil source.

type IssueUserOutput

type IssueUserOutput struct {
	ID        int64  `json:"id"`
	State     string `json:"state"`
	WebURL    string `json:"web_url"`
	Name      string `json:"name"`
	AvatarURL string `json:"avatar_url"`
	Username  string `json:"username"`
}

IssueUserOutput is the documented 6-field user object that appears inside issue resources (author / assignees[] / closed_by) and inside the nested epic.author pointer. The fields mirror the per-resource JSON shape documented for issues (the full BasicUser shape omits `state` on this resource, so IssueUserOutput intentionally omits it too — see the package-local shapes.go history for the audit notes).

func NewIssueUserOutputFromBasicUser

func NewIssueUserOutputFromBasicUser(u gl.BasicUser) IssueUserOutput

NewIssueUserOutputFromBasicUser converts a gl.BasicUser into the canonical issue-user shape, populating only the documented fields.

func NewIssueUserOutputFromEpicAuthor

func NewIssueUserOutputFromEpicAuthor(u *gl.EpicAuthor) *IssueUserOutput

NewIssueUserOutputFromEpicAuthor converts a *gl.EpicAuthor pointer into the canonical issue-user shape. gl.EpicAuthor has the same 6 field layout as gl.BasicUser so the conversion is structural — the type distinction in the SDK is purely for documentation.

func NewIssueUserOutputFromIssueAssignee

func NewIssueUserOutputFromIssueAssignee(a *gl.IssueAssignee) *IssueUserOutput

NewIssueUserOutputFromIssueAssignee converts a single *gl.IssueAssignee pointer into the canonical issue-user shape (used for the deprecated singular Assignee field on gl.Issue).

func NewIssueUserOutputFromIssueAuthor

func NewIssueUserOutputFromIssueAuthor(u *gl.IssueAuthor) *IssueUserOutput

NewIssueUserOutputFromIssueAuthor converts a *gl.IssueAuthor pointer into the canonical issue-user shape. gl.IssueAuthor is the dedicated author type on gl.Issue (distinct from gl.BasicUser so the documented "issues have an author" reference is preserved) — fields are the same 6 we surface elsewhere.

func NewIssueUserOutputFromIssueCloser

func NewIssueUserOutputFromIssueCloser(u *gl.IssueCloser) *IssueUserOutput

NewIssueUserOutputFromIssueCloser converts a *gl.IssueCloser pointer into the canonical issue-user shape. Same 6-field layout as the other issue-user types.

func NewIssueUserOutputFromPointer

func NewIssueUserOutputFromPointer(u *gl.BasicUser) *IssueUserOutput

NewIssueUserOutputFromPointer converts a *gl.BasicUser, returning nil for a nil source so call sites can pass through SDK pointers without an extra nil check.

func NewIssueUserOutputsFromIssueAssignees

func NewIssueUserOutputsFromIssueAssignees(as []*gl.IssueAssignee) []*IssueUserOutput

NewIssueUserOutputsFromIssueAssignees converts a slice of gl.IssueAssignee, skipping nil entries and returning nil for an empty / all-nil input.

type IterationOutput

type IterationOutput struct {
	ID          int64  `json:"id"`
	IID         int64  `json:"iid"`
	Sequence    int64  `json:"sequence"`
	GroupID     int64  `json:"group_id"`
	Title       string `json:"title"`
	Description string `json:"description"`
	State       int64  `json:"state"`
	WebURL      string `json:"web_url"`
	CreatedAt   string `json:"created_at,omitempty"`
	UpdatedAt   string `json:"updated_at,omitempty"`
	StartDate   string `json:"start_date,omitempty"`
	DueDate     string `json:"due_date,omitempty"`
}

IterationOutput mirrors gl.Iteration (the iteration object surfaced on issue.iteration). State is an int per the SDK type (an enum of 0=upcoming, 1=current, 2=closed).

func NewIterationOutput

func NewIterationOutput(it *gl.Iteration) *IterationOutput

NewIterationOutput converts a gl.Iteration pointer into the canonical iteration object, returning nil for a nil source.

func NewIterationOutputFromGroupIteration

func NewIterationOutputFromGroupIteration(it *gl.GroupIteration) *IterationOutput

NewIterationOutputFromGroupIteration converts a gl.GroupIteration pointer (the type surfaced on gl.Issue.Iteration) into the canonical iteration object. gl.GroupIteration has the same 11 fields as gl.Iteration but with a separate type identity for documentation.

func NewIterationOutputFromProjectIteration

func NewIterationOutputFromProjectIteration(it *gl.ProjectIteration) *IterationOutput

NewIterationOutputFromProjectIteration converts a gl.ProjectIteration pointer (the type surfaced on board lists) into the canonical iteration object. gl.ProjectIteration has the same field layout as gl.Iteration but with a separate type identity for documentation.

type JSONDepthScanner

type JSONDepthScanner struct {
	// contains filtered or unexported fields
}

JSONDepthScanner measures the nesting depth of a JSON value one chunk at a time, so a body can be judged as it streams rather than after it is buffered.

It counts brackets and braces outside string literals, which is all that nesting depth is, and deliberately does not validate the JSON: a scanner that also parsed would be a second, divergent implementation of a decoder this server already runs twice. Miscounting a malformed document is harmless because the decoder behind it refuses that document anyway, and the count is never an undercount for the shape this exists to stop.

The zero value is not usable; construct one with NewJSONDepthScanner.

func NewJSONDepthScanner

func NewJSONDepthScanner(limit int) *JSONDepthScanner

NewJSONDepthScanner returns a scanner that trips once nesting passes limit.

func (*JSONDepthScanner) Exceeded

func (s *JSONDepthScanner) Exceeded() bool

Exceeded reports whether any chunk scanned so far passed the limit.

func (*JSONDepthScanner) Scan

func (s *JSONDepthScanner) Scan(chunk []byte) bool

Scan folds the next chunk of bytes into the measurement and reports whether the limit has been passed. State carries across calls, so the chunk boundaries an io.Reader happens to produce do not change the answer.

type KeyExtra

type KeyExtra struct {
	ExpiresAt  *time.Time `json:"expires_at"`
	LastUsedAt *time.Time `json:"last_used_at"`
	UsageType  string     `json:"usage_type"`
}

KeyExtra is what lib/api/entities/ssh_key.rb sends on a key that client-go's Key does not carry: the expiry, the last use and the usage type, all sent on every key.

func CapturedKey

func CapturedKey(capture *gitlabclient.ResponseCapture) (KeyExtra, error)

CapturedKey reads, off the captured answer to a request for one key, the fields client-go's Key does not model.

type KeysetPaginationInput

type KeysetPaginationInput struct {
	Pagination string `` /* 185-byte string literal not displayed */
	PageToken  string `` /* 186-byte string literal not displayed */
}

KeysetPaginationInput holds GitLab keyset-pagination parameters for list endpoints that support keyset pagination in addition to offset pagination. Embed it alongside PaginationInput on a list input and wire it with ApplyListOptions. Keyset pagination is more efficient than offset pagination for deep pages of large, ordered result sets.

type LabelDetailsOutput

type LabelDetailsOutput struct {
	ID              int64  `json:"id"`
	Name            string `json:"name"`
	Color           string `json:"color"`
	Description     string `json:"description"`
	DescriptionHTML string `json:"description_html"`
	TextColor       string `json:"text_color"`
}

LabelDetailsOutput mirrors gl.LabelDetails (the inline label details surfaced on labels array inside issue and MR resources).

func NewLabelDetailsOutputs

func NewLabelDetailsOutputs(details []*gl.LabelDetails) []*LabelDetailsOutput

NewLabelDetailsOutputs converts a slice of gl.LabelDetails, skipping nil entries and returning nil for an empty / all-nil input.

type LabelExtra

type LabelExtra struct {
	DescriptionHTML string `json:"description_html"`
}

LabelExtra is what lib/api/entities/label.rb sends on a label that client-go's Label and GroupLabel do not carry: the description rendered as HTML, sent on every label.

func CapturedLabel

func CapturedLabel(capture *gitlabclient.ResponseCapture) (LabelExtra, error)

CapturedLabel reads, off the captured answer to a request for one label, the field client-go's label structs do not model.

func CapturedLabels

func CapturedLabels(capture *gitlabclient.ResponseCapture, decoded int) ([]LabelExtra, error)

CapturedLabels reads the same off a list answer, one extra per label in order, the count held to what the SDK decoded.

type LabelMarkdown

type LabelMarkdown struct {
	ID                     int64
	Name                   string
	Color                  string
	Description            string
	OpenIssuesCount        int64
	ClosedIssuesCount      int64
	OpenMergeRequestsCount int64
	Priority               int64
	PrioritySpecified      bool
	IsProjectLabel         bool
	Subscribed             bool
}

LabelMarkdown holds the common fields rendered for project and group labels.

type LabelMarkdownOptions

type LabelMarkdownOptions struct {
	DetailTitle       string
	ListTitle         string
	EmptyListText     string
	DetailHints       []string
	ListHints         []string
	EscapeDescription bool
}

LabelMarkdownOptions controls label detail and list Markdown copy.

type LastPipelineOutput

type LastPipelineOutput struct {
	ID        int64  `json:"id"`
	IID       int64  `json:"iid,omitempty"`
	ProjectID int64  `json:"project_id,omitempty"`
	Status    string `json:"status,omitempty"`
	Source    string `json:"source,omitempty"`
	Ref       string `json:"ref,omitempty"`
	SHA       string `json:"sha,omitempty"`
	Name      string `json:"name,omitempty"`
	WebURL    string `json:"web_url,omitempty"`
	CreatedAt string `json:"created_at,omitempty"`
	UpdatedAt string `json:"updated_at,omitempty"`
}

LastPipelineOutput mirrors gl.PipelineInfo, the pipeline summary embedded in a commit payload as last_pipeline (commits, branch head commits).

func NewLastPipelineOutput

func NewLastPipelineOutput(p *gl.PipelineInfo) *LastPipelineOutput

NewLastPipelineOutput maps gl.PipelineInfo to *LastPipelineOutput, or nil when the commit has no associated pipeline.

type LicenseTemplateExtra

type LicenseTemplateExtra struct {
	Popular bool `json:"popular"`
}

LicenseTemplateExtra is whether a license template is one of the popular ones GitLab offers first, exposed under no condition.

func CapturedLicenseTemplate

func CapturedLicenseTemplate(capture *gitlabclient.ResponseCapture) (LicenseTemplateExtra, error)

CapturedLicenseTemplate reads it off the captured answer to a request for one template.

func CapturedLicenseTemplates

func CapturedLicenseTemplates(capture *gitlabclient.ResponseCapture, decoded int) ([]LicenseTemplateExtra, error)

CapturedLicenseTemplates reads the same off a list answer, one extra per template in order, the count held to what the SDK decoded.

type LinePositionOutput

type LinePositionOutput struct {
	LineCode string `json:"line_code,omitempty"`
	Type     string `json:"type,omitempty"`
	OldLine  int64  `json:"old_line,omitempty"`
	NewLine  int64  `json:"new_line,omitempty"`
}

LinePositionOutput mirrors gl.LinePosition: one endpoint (start or end) of a multi-line diff note position.

func NewLinePositionOutput

func NewLinePositionOutput(p *gl.LinePosition) *LinePositionOutput

NewLinePositionOutput converts a *gl.LinePosition into the canonical-key line-position object, returning nil when the SDK value is nil.

type LineRangeOutput

type LineRangeOutput struct {
	Start *LinePositionOutput `json:"start,omitempty"`
	End   *LinePositionOutput `json:"end,omitempty"`
}

LineRangeOutput mirrors gl.LineRange (the start/end of a multi-line diff note position). The full *LinePositionOutput objects are surfaced on the canonical `start` / `end` keys (mirroring gl.LineRange.StartRange / gl.LineRange.EndRange, whose JSON tags are `start` / `end`).

func NewLineRangeOutput

func NewLineRangeOutput(lr *gl.LineRange) *LineRangeOutput

NewLineRangeOutput converts a *gl.LineRange into the line-range object, returning nil when absent (or when both endpoints are nil).

type LineType

type LineType int

LineType classifies a line within a unified diff hunk.

const (
	// LineContext identifies an unchanged line present in both old and new file versions.
	LineContext LineType = iota // Unchanged line (present in both old and new file)
	// LineAdded identifies a line present only in the new file version.
	LineAdded // Added line (present only in new file)
	// LineRemoved identifies a line present only in the old file version.
	LineRemoved // Removed line (present only in old file)
)

type LinksOutput

type LinksOutput struct {
	Self                   string `json:"self,omitempty"`
	EditURL                string `json:"edit_url,omitempty"`
	OpenedIssuesURL        string `json:"opened_issues_url,omitempty"`
	OpenedMergeRequestsURL string `json:"opened_merge_requests_url,omitempty"`
	MergedMergeRequestsURL string `json:"merged_merge_requests_url,omitempty"`
	ClosedIssuesURL        string `json:"closed_issues_url,omitempty"`
	ClosedMergeRequestsURL string `json:"closed_merge_requests_url,omitempty"`
}

LinksOutput mirrors gl.ReleaseLinks (the _links object on a release). Per the documented release-by-tag response GitLab returns URL strings for self, edit, opened_issues, opened_merge_requests, merged_merge_requests, and closed_merge_requests. The JSON tags follow the SDK's snake_case pluralisation (opened_issues_url, etc.) which differs from the older opened_issues / merged_issues / closed_issues names used in some docs.

func NewLinksOutput

func NewLinksOutput(l gl.ReleaseLinks) *LinksOutput

NewLinksOutput converts a gl.ReleaseLinks into the canonical-key links object, returning nil when the SDK value has every URL field empty.

type LintExtra

type LintExtra struct {
	Jobs []LintJobOutput `json:"jobs"`
}

LintExtra is what lib/api/entities/ci/lint/result.rb sends that client-go's ProjectLintResult does not carry.

func CapturedLint

func CapturedLint(capture *gitlabclient.ResponseCapture) (LintExtra, error)

CapturedLint reads, off the captured answer to a lint request, the jobs array client-go does not model.

type LintJobOutput

type LintJobOutput struct {
	Name         string   `json:"name"`
	Stage        string   `json:"stage"`
	BeforeScript []string `json:"before_script"`
	Script       []string `json:"script"`
	AfterScript  []string `json:"after_script"`
	TagList      []string `json:"tag_list"`
	Only         any      `json:"only,omitempty"`
	Except       any      `json:"except,omitempty"`
	Environment  any      `json:"environment,omitempty"`
	When         string   `json:"when"`
	AllowFailure bool     `json:"allow_failure"`
	Needs        any      `json:"needs,omitempty"`
}

LintJobOutput is one job of the jobs array a CI lint answer carries when include_jobs is set, built by lib/gitlab/ci/lint.rb from the processed configuration; client-go's ProjectLintResult does not carry the array. A static check carries only, except and needs as the configuration spelled them, so those three keep the JSON shape GitLab sent, and environment is the name on a pipeline simulation and the configuration on a static check.

type MRMilestoneOutput

type MRMilestoneOutput struct {
	ID          int64  `json:"id"`
	IID         int64  `json:"iid"`
	GroupID     int64  `json:"group_id"`
	ProjectID   int64  `json:"project_id"`
	Title       string `json:"title"`
	Description string `json:"description"`
	State       string `json:"state"`
	WebURL      string `json:"web_url"`
	StartDate   string `json:"start_date,omitempty"`
	DueDate     string `json:"due_date,omitempty"`
	CreatedAt   string `json:"created_at,omitempty"`
	UpdatedAt   string `json:"updated_at,omitempty"`
	Expired     *bool  `json:"expired,omitempty"`
}

MRMilestoneOutput mirrors gl.Milestone as surfaced inside merge request and issue resources. Distinct from toolutil.MilestoneOutput (the release-milestone shape) — see file-level comment for the rationale and field-level deltas.

func NewMRMilestoneOutputs

func NewMRMilestoneOutputs(ms []*gl.Milestone) []*MRMilestoneOutput

NewMRMilestoneOutputs converts a slice of gl.Milestone, skipping nil entries and returning nil for an empty / all-nil input.

type MemberExtra

type MemberExtra struct {
	Locked            bool                `json:"locked"`
	PublicEmail       string              `json:"public_email"`
	MembershipState   string              `json:"membership_state"`
	TwoFactorEnabled  *bool               `json:"two_factor_enabled"`
	GroupSAMLIdentity *SAMLIdentityOutput `json:"group_saml_identity"`
	GroupSCIMIdentity *SCIMIdentityOutput `json:"group_scim_identity"`
	Override          *bool               `json:"override"`
	// AvatarPath is the avatar as a path on the instance rather than a full
	// URL, and CustomAttributes the attributes an administrator who asked for
	// them receives. Both come from the user object merged into the member.
	AvatarPath       string                  `json:"avatar_path"`
	CustomAttributes []CustomAttributeOutput `json:"custom_attributes"`
}

MemberExtra is what lib/api/entities/member.rb sends on a member that client-go's GroupMember and ProjectMember do not carry, read from the captured response beside the SDK's own decode (ADR-0021). locked is on every member and membership_state on every member of an Enterprise instance; the rest are sent when their condition holds, so each is a pointer that stays nil otherwise. public_email and group_saml_identity are on GroupMember, and only a project member reads them from here.

func CapturedMember

func CapturedMember(capture *gitlabclient.ResponseCapture) (MemberExtra, error)

CapturedMember reads, off the captured answer to a request for one member, the fields client-go's member structs do not model. A body that decoded for the SDK and does not for MemberExtra is reported, since the fault is then in the type naming the fields.

func CapturedMembers

func CapturedMembers(capture *gitlabclient.ResponseCapture, decoded int) ([]MemberExtra, error)

CapturedMembers reads the same off a list answer, one extra per member in order, the count held to what the SDK decoded as CapturedNotes holds it.

type MemberRoleOutput

type MemberRoleOutput struct {
	ID                         int64  `json:"id"`
	Name                       string `json:"name"`
	Description                string `json:"description,omitempty"`
	GroupID                    int64  `json:"group_id"`
	BaseAccessLevel            int    `json:"base_access_level"`
	AdminCICDVariables         bool   `json:"admin_cicd_variables,omitempty"`
	AdminComplianceFramework   bool   `json:"admin_compliance_framework,omitempty"`
	AdminGroupMembers          bool   `json:"admin_group_member,omitempty"`
	AdminMergeRequests         bool   `json:"admin_merge_request,omitempty"`
	AdminPushRules             bool   `json:"admin_push_rules,omitempty"`
	AdminTerraformState        bool   `json:"admin_terraform_state,omitempty"`
	AdminVulnerability         bool   `json:"admin_vulnerability,omitempty"`
	AdminWebHook               bool   `json:"admin_web_hook,omitempty"`
	ArchiveProject             bool   `json:"archive_project,omitempty"`
	ManageDeployTokens         bool   `json:"manage_deploy_tokens,omitempty"`
	ManageGroupAccessTokens    bool   `json:"manage_group_access_tokens,omitempty"`
	ManageMergeRequestSettings bool   `json:"manage_merge_request_settings,omitempty"`
	ManageProjectAccessTokens  bool   `json:"manage_project_access_tokens,omitempty"`
	ManageSecurityPolicyLink   bool   `json:"manage_security_policy_link,omitempty"`
	ReadCode                   bool   `json:"read_code,omitempty"`
	ReadRunners                bool   `json:"read_runners,omitempty"`
	ReadDependency             bool   `json:"read_dependency,omitempty"`
	ReadVulnerability          bool   `json:"read_vulnerability,omitempty"`
	RemoveGroup                bool   `json:"remove_group,omitempty"`
	RemoveProject              bool   `json:"remove_project,omitempty"`
}

MemberRoleOutput mirrors gl.MemberRole (the member_role object). Custom member roles are an Enterprise (Premium/Ultimate) feature; the object is nil on instances or members without a custom role. All permission flags are surfaced for 1:1 SDK fidelity.

func NewMemberRoleOutput

func NewMemberRoleOutput(r *gl.MemberRole) *MemberRoleOutput

NewMemberRoleOutput mirrors a gl.MemberRole into the shared output shape, returning nil when the SDK value is nil.

type MemberUserOutput

type MemberUserOutput struct {
	ID        int64  `json:"id"`
	Username  string `json:"username"`
	Name      string `json:"name"`
	State     string `json:"state"`
	AvatarURL string `json:"avatar_url,omitempty"`
	WebURL    string `json:"web_url,omitempty"`
}

MemberUserOutput mirrors gl.MemberCreatedBy (the created_by object).

func NewMemberUserOutput

func NewMemberUserOutput(u *gl.MemberCreatedBy) *MemberUserOutput

NewMemberUserOutput mirrors a gl.MemberCreatedBy into the shared output shape, returning nil when the SDK value is nil.

type MergeRequestDiffExtra

type MergeRequestDiffExtra struct {
	PatchIDSHA string `json:"patch_id_sha"`
}

MergeRequestDiffExtra is the patch id of a merge request version, which identifies the change independently of the commits carrying it. The diff entity exposes it under no condition.

func CapturedMergeRequestDiff

func CapturedMergeRequestDiff(capture *gitlabclient.ResponseCapture) (MergeRequestDiffExtra, error)

CapturedMergeRequestDiff reads it off the captured answer to a request for one version.

func CapturedMergeRequestDiffs

func CapturedMergeRequestDiffs(capture *gitlabclient.ResponseCapture, decoded int) ([]MergeRequestDiffExtra, error)

CapturedMergeRequestDiffs reads the same off a list answer, one extra per version in order, the count held to what the SDK decoded.

type MergeRequestExtra

type MergeRequestExtra struct {
	ApprovalsBeforeMerge *int64 `json:"approvals_before_merge"`
	MergeStatus          string `json:"merge_status"`
	Reference            string `json:"reference"`
	WorkInProgress       bool   `json:"work_in_progress"`
	TitleHTML            string `json:"title_html"`
	DescriptionHTML      string `json:"description_html"`
}

MergeRequestExtra is what lib/api/entities/merge_request_basic.rb sends that client-go's BasicMergeRequest does not carry.

Four of the six are the older spelling of something the entity also sends under a newer name, and GitLab still sends both: merge_status beside detailed_merge_status, reference beside references, work_in_progress beside draft, and approvals_before_merge beside the approval rules API. Deprecated is not absent, and a caller reading a merge request through this server should see what GitLab put on the wire.

title_html and description_html are the exception: the entity exposes them under the render_html presenter option, and of every route GitLab mounts only GET /projects/:id/merge_requests/:merge_request_iid declares render_html as a request parameter. They arrive on that one response, when the caller asked for them, and on no other, which is why the output shape publishes them omitempty and why a type serving only the other routes leaves them out altogether.

approvals_before_merge is a pointer because GitLab sends null where no approval count applies, which a bare int64 would flatten into zero, a number that means something else.

func CapturedMergeRequest

func CapturedMergeRequest(capture *gitlabclient.ResponseCapture) (MergeRequestExtra, error)

CapturedMergeRequest reads them off the captured answer to a request that returned one merge request.

func CapturedMergeRequests

func CapturedMergeRequests(capture *gitlabclient.ResponseCapture, decoded int) ([]MergeRequestExtra, error)

CapturedMergeRequests reads the same off a list answer, one extra per merge request in order, the count held to what the SDK decoded.

type MergeRequestOutput

type MergeRequestOutput struct {
	HintableOutput
	ID                          int64                       `json:"id"`
	IID                         int64                       `json:"iid"`
	ProjectID                   int64                       `json:"project_id"`
	SourceProjectID             int64                       `json:"source_project_id,omitempty"`
	TargetProjectID             int64                       `json:"target_project_id,omitempty"`
	Title                       string                      `json:"title"`
	Description                 string                      `json:"description"`
	State                       string                      `json:"state"`
	Imported                    bool                        `json:"imported,omitempty"`
	ImportedFrom                string                      `json:"imported_from,omitempty"`
	SourceBranch                string                      `json:"source_branch"`
	TargetBranch                string                      `json:"target_branch"`
	WebURL                      string                      `json:"web_url"`
	DetailedMergeStatus         string                      `json:"detailed_merge_status,omitempty"`
	Draft                       bool                        `json:"draft"`
	WorkInProgress              bool                        `json:"work_in_progress,omitempty"`
	HasConflicts                bool                        `json:"has_conflicts"`
	BlockingDiscussionsResolved bool                        `json:"blocking_discussions_resolved"`
	Squash                      bool                        `json:"squash,omitempty"`
	SquashOnMerge               bool                        `json:"squash_on_merge,omitempty"`
	MergeWhenPipelineSucceeds   bool                        `json:"merge_when_pipeline_succeeds,omitempty"`
	ShouldRemoveSourceBranch    bool                        `json:"should_remove_source_branch,omitempty"`
	AllowMaintainerToPush       bool                        `json:"allow_maintainer_to_push,omitempty"`
	DiscussionLocked            bool                        `json:"discussion_locked"`
	RebaseInProgress            bool                        `json:"rebase_in_progress,omitempty"`
	Author                      *BasicUserOutput            `json:"author,omitempty"`
	Assignee                    *BasicUserOutput            `json:"assignee,omitempty"`
	MergeUser                   *BasicUserOutput            `json:"merge_user,omitempty"`
	MergedBy                    *BasicUserOutput            `json:"merged_by,omitempty"`
	ClosedBy                    *BasicUserOutput            `json:"closed_by,omitempty"`
	Assignees                   []*BasicUserOutput          `json:"assignees"`
	Reviewers                   []*BasicUserOutput          `json:"reviewers"`
	Labels                      []string                    `json:"labels"`
	LabelDetails                []*LabelDetailsOutput       `json:"label_details,omitempty"`
	Milestone                   *MRMilestoneOutput          `json:"milestone,omitempty"`
	References                  *ReferencesOutput           `json:"references,omitempty"`
	SHA                         string                      `json:"sha,omitempty"`
	MergeCommitSHA              string                      `json:"merge_commit_sha,omitempty"`
	MergeError                  string                      `json:"merge_error,omitempty"`
	ChangesCount                string                      `json:"changes_count,omitempty"`
	DivergedCommitsCount        int64                       `json:"diverged_commits_count,omitempty"`
	Upvotes                     int64                       `json:"upvotes,omitempty"`
	Downvotes                   int64                       `json:"downvotes,omitempty"`
	SquashCommitSHA             string                      `json:"squash_commit_sha,omitempty"`
	ForceRemoveSourceBranch     bool                        `json:"force_remove_source_branch,omitempty"`
	AllowCollaboration          bool                        `json:"allow_collaboration,omitempty"`
	MergeAfter                  string                      `json:"merge_after,omitempty"`
	TaskCompletionStatus        *TaskCompletionStatusOutput `json:"task_completion_status,omitempty"`
	TimeStats                   *TimeStatsOutput            `json:"time_stats,omitempty"`
	Subscribed                  bool                        `json:"subscribed,omitempty"`
	FirstContribution           bool                        `json:"first_contribution,omitempty"`
	User                        *MergeRequestUserOutput     `json:"user,omitempty"`
	DiffRefs                    *DiffRefsOutput             `json:"diff_refs,omitempty"`
	Pipeline                    *PipelineInfoOutput         `json:"pipeline,omitempty"`
	HeadPipeline                *PipelineOutput             `json:"head_pipeline,omitempty"`
	LatestBuildStartedAt        string                      `json:"latest_build_started_at,omitempty"`
	LatestBuildFinishedAt       string                      `json:"latest_build_finished_at,omitempty"`
	FirstDeployedToProductionAt string                      `json:"first_deployed_to_production_at,omitempty"`
	CreatedAt                   string                      `json:"created_at"`
	UpdatedAt                   string                      `json:"updated_at"`
	MergedAt                    string                      `json:"merged_at,omitempty"`
	ClosedAt                    string                      `json:"closed_at,omitempty"`
	PreparedAt                  string                      `json:"prepared_at,omitempty"`
	UserNotesCount              int64                       `json:"user_notes_count,omitempty"`
	// The keys GitLab's merge request entity sends that client-go's structs do
	// not model, read from the captured response beside the SDK's own decode
	// (ADR-0021) and described on [MergeRequestExtra]. approvals_before_merge
	// carries the tier the sibling shapes already give it, since the count only
	// exists where merge request approvals are licensed.
	ApprovalsBeforeMerge *int64 `json:"approvals_before_merge,omitempty" tier:"premium"`
	MergeStatus          string `json:"merge_status,omitempty"`
	Reference            string `json:"reference,omitempty"`
	TitleHTML            string `json:"title_html,omitempty"`
	DescriptionHTML      string `json:"description_html,omitempty"`
}

MergeRequestOutput mirrors gl.MergeRequest (the full merge-request payload returned by MR list/get/create/update endpoints). It is the canonical Output shape shared by the mergerequests and deploymentmergerequests domains via type aliases, eliminating the ~80-line duplicated struct block identified by SonarCloud while avoiding cross-package import cycles (ADR-0004).

HintableOutput is embedded first so next_steps appears at the top of the serialized JSON, giving LLMs workflow guidance before parsing the payload.

func (*MergeRequestOutput) ApplyExtra

func (o *MergeRequestOutput) ApplyExtra(extra MergeRequestExtra)

ApplyExtra fills the keys read off the captured response.

It is a method rather than six assignments at each call site because the merge request converters are reached from four packages, and a key added to MergeRequestExtra that one of them forgot would be a schema field nothing ever fills.

work_in_progress is filled from here even though client-go models it, because it models it on MergeRequest alone: a list decodes into BasicMergeRequest, which does not carry it, so before this the key was published on every list and set on none of them. The capture reads the same bytes the SDK decoded, so the two answers cannot disagree where both exist.

type MergeRequestUserOutput

type MergeRequestUserOutput struct {
	CanMerge bool `json:"can_merge"`
}

MergeRequestUserOutput mirrors gl.MergeRequestUser (the user object inside MR.user_notes_count type contexts — only the CanMerge flag is part of the documented subset).

func NewMergeRequestUserOutput

func NewMergeRequestUserOutput(u *gl.MergeRequestUser) *MergeRequestUserOutput

NewMergeRequestUserOutput converts a gl.MergeRequestUser pointer into the canonical-key MR user object, returning nil for a nil source.

type MetaSchemaActionEntry

type MetaSchemaActionEntry struct {
	Action      string `json:"action"`
	SchemaURI   string `json:"schema_uri"`
	Destructive bool   `json:"destructive"`
}

MetaSchemaActionEntry describes one meta-tool action in the tool-call index.

type MetaSchemaDiscoveryIndex

type MetaSchemaDiscoveryIndex struct {
	URITemplate string                `json:"uri_template"`
	ToolCount   int                   `json:"tool_count"`
	ActionCount int                   `json:"action_count"`
	Tools       []MetaSchemaToolEntry `json:"tools"`
}

MetaSchemaDiscoveryIndex is a model-controlled schema discovery payload.

func BuildMetaSchemaDiscoveryIndex

func BuildMetaSchemaDiscoveryIndex(routes map[string]ActionMap) MetaSchemaDiscoveryIndex

BuildMetaSchemaDiscoveryIndex builds the richer tool-call schema index payload.

func BuildMetaSchemaDiscoveryIndexForTool

func BuildMetaSchemaDiscoveryIndexForTool(routes map[string]ActionMap, tool string) (MetaSchemaDiscoveryIndex, bool)

BuildMetaSchemaDiscoveryIndexForTool builds the tool-call index for one meta-tool.

type MetaSchemaIndex

type MetaSchemaIndex struct {
	URITemplate string                 `json:"uri_template"`
	Tools       []MetaSchemaIndexEntry `json:"tools"`
}

MetaSchemaIndex is the payload returned by the schema index resource.

func BuildMetaSchemaIndex

func BuildMetaSchemaIndex(routes map[string]ActionMap) MetaSchemaIndex

BuildMetaSchemaIndex builds the resource-compatible schema index payload.

type MetaSchemaIndexEntry

type MetaSchemaIndexEntry struct {
	Tool    string   `json:"tool"`
	Actions []string `json:"actions"`
}

MetaSchemaIndexEntry is a single tool entry in the resource index payload.

type MetaSchemaRegistry

type MetaSchemaRegistry struct {
	// contains filtered or unexported fields
}

MetaSchemaRegistry stores the visible meta-tool route snapshot used by model-controlled schema discovery actions.

func NewMetaSchemaRegistry

func NewMetaSchemaRegistry(routes map[string]ActionMap) *MetaSchemaRegistry

NewMetaSchemaRegistry creates a registry initialized with a route snapshot.

func (*MetaSchemaRegistry) Routes

func (r *MetaSchemaRegistry) Routes() map[string]ActionMap

Routes returns a snapshot of the registry contents, taken with CloneMetaSchemaRoutes: later SetRoutes calls do not reach it, and the schemas in it are shared and must not be mutated.

func (*MetaSchemaRegistry) SetRoutes

func (r *MetaSchemaRegistry) SetRoutes(routes map[string]ActionMap)

SetRoutes replaces the registry contents with a snapshot of routes, taken with CloneMetaSchemaRoutes: the maps are the registry's own, the schemas inside them are shared with the caller and frozen.

type MetaSchemaToolEntry

type MetaSchemaToolEntry struct {
	Tool        string                  `json:"tool"`
	ActionCount int                     `json:"action_count"`
	Actions     []MetaSchemaActionEntry `json:"actions"`
}

MetaSchemaToolEntry describes one meta-tool in the tool-call index.

type MetaToolInput

type MetaToolInput struct {
	Action string         `json:"action" jsonschema:"Action to perform. See the tool description for available actions and their parameters."`
	Params map[string]any `` /* 147-byte string literal not displayed */
}

MetaToolInput is the common input for all meta-tools. The LLM sends an action name and a params object; the dispatcher routes to the underlying handler function and deserializes params into the action-specific input struct.

type MilestoneExtra

type MilestoneExtra struct {
	WebURL    string `json:"web_url"`
	ProjectID int64  `json:"project_id"`
}

MilestoneExtra is what GitLab's milestone entity sends that a group milestone struct does not carry: the milestone's own page, exposed under no condition, and the project a project-scoped milestone belongs to, exposed only when there is one, so a group milestone leaves it zero.

func CapturedMilestone

func CapturedMilestone(capture *gitlabclient.ResponseCapture) (MilestoneExtra, error)

CapturedMilestone reads them off the captured answer to a request for one milestone.

func CapturedMilestones

func CapturedMilestones(capture *gitlabclient.ResponseCapture, decoded int) ([]MilestoneExtra, error)

CapturedMilestones reads the same off a list answer, one extra per milestone in order, the count held to what the SDK decoded.

type MilestoneIssueStatsOutput

type MilestoneIssueStatsOutput struct {
	Total  int64 `json:"total,omitempty"`
	Closed int64 `json:"closed,omitempty"`
}

MilestoneIssueStatsOutput mirrors gl.ReleaseMilestoneIssueStats (the total / closed counts object surfaced under each associated milestone on a release).

func NewMilestoneIssueStatsOutput

func NewMilestoneIssueStatsOutput(s *gl.ReleaseMilestoneIssueStats) *MilestoneIssueStatsOutput

NewMilestoneIssueStatsOutput converts a gl.ReleaseMilestoneIssueStats into the canonical-key stats object.

type MilestoneOutput

type MilestoneOutput struct {
	ID          int64                      `json:"id,omitempty"`
	IID         int64                      `json:"iid,omitempty"`
	ProjectID   int64                      `json:"project_id,omitempty"`
	Title       string                     `json:"title"`
	Description string                     `json:"description,omitempty"`
	State       string                     `json:"state,omitempty"`
	WebURL      string                     `json:"web_url,omitempty"`
	CreatedAt   string                     `json:"created_at,omitempty"`
	UpdatedAt   string                     `json:"updated_at,omitempty"`
	StartDate   string                     `json:"start_date,omitempty"`
	DueDate     string                     `json:"due_date,omitempty"`
	IssueStats  *MilestoneIssueStatsOutput `json:"issue_stats,omitempty"`
}

MilestoneOutput mirrors gl.ReleaseMilestone (the milestone object surfaced on the release's milestones array). Timestamps are surfaced as RFC 3339 strings via toolutil.FormatTimePtr / FormatISOTimePtr.

func NewMilestoneOutputs

func NewMilestoneOutputs(ms []*gl.ReleaseMilestone) []*MilestoneOutput

NewMilestoneOutputs converts a slice of gl.ReleaseMilestone, skipping nil elements and returning nil for an empty input. Timestamps are formatted as RFC 3339 (or YYYY-MM-DD for ISOTime).

type NamespaceBasicOutput

type NamespaceBasicOutput struct {
	ID        int64  `json:"id"`
	Name      string `json:"name"`
	Path      string `json:"path"`
	Kind      string `json:"kind"`
	FullPath  string `json:"full_path"`
	ParentID  int64  `json:"parent_id,omitempty"`
	AvatarURL string `json:"avatar_url,omitempty"`
	WebURL    string `json:"web_url,omitempty"`
}

NamespaceBasicOutput mirrors lib/api/entities/namespace_basic.rb, the group object GitLab renders on a todo raised in a group rather than a project.

type NamespaceExtra

type NamespaceExtra struct {
	ProjectsCount                    int64      `json:"projects_count"`
	RootRepositorySize               int64      `json:"root_repository_size"`
	SharedRunnersMinutesLimit        *int64     `json:"shared_runners_minutes_limit"`
	ExtraSharedRunnersMinutesLimit   *int64     `json:"extra_shared_runners_minutes_limit"`
	AdditionalPurchasedStorageSize   *int64     `json:"additional_purchased_storage_size"`
	AdditionalPurchasedStorageEndsOn string     `json:"additional_purchased_storage_ends_on"`
	MaxSeatsUsedChangedAt            *time.Time `json:"max_seats_used_changed_at"`
	EndDate                          string     `json:"end_date"`
}

NamespaceExtra is what GitLab's namespace entity sends to a caller allowed to see it. An administrator asking about a group is told how many projects it holds and how much room their repositories take; a caller who may change the namespace's limits is told the compute minutes and the purchased storage; and a namespace with a subscription carries when that subscription ends and when its seat high-water mark last moved.

func CapturedNamespace

func CapturedNamespace(capture *gitlabclient.ResponseCapture) (NamespaceExtra, error)

CapturedNamespace reads them off the captured answer to a request for one namespace.

func CapturedNamespaces

func CapturedNamespaces(capture *gitlabclient.ResponseCapture, decoded int) ([]NamespaceExtra, error)

CapturedNamespaces reads the same off a list answer, one extra per namespace in order, the count held to what the SDK decoded.

type NoteExtra

type NoteExtra struct {
	Confidential    bool               `json:"confidential"`
	Imported        bool               `json:"imported"`
	ImportedFrom    string             `json:"imported_from"`
	CommandsChanges map[string]any     `json:"commands_changes"`
	Suggestions     []SuggestionOutput `json:"suggestions"`
	Author          NoteUserExtra      `json:"author"`
	ResolvedBy      NoteUserExtra      `json:"resolved_by"`
}

NoteExtra is what GitLab's Note entity sends that client-go's Note does not model, read from the captured response beside the SDK's own decoding (see CapturedNote). `confidential` is here too: client-go carries it as a deprecated field some converters read and others mirror from `internal`, and reading GitLab's own value ends that difference.

func CapturedNote

func CapturedNote(capture *gitlabclient.ResponseCapture) (NoteExtra, error)

CapturedNote reads, from the captured answer of a call that returned one note, what GitLab sends that client-go's Note does not model.

func CapturedNotes

func CapturedNotes(capture *gitlabclient.ResponseCapture, decoded int) ([]NoteExtra, error)

CapturedNotes reads the same from the captured answer of a call that returned a list of notes, one extra per note in the list's order. The count is held to what the SDK decoded: the two read the same bytes, so a difference is a fault in this reader and not in GitLab's answer.

type NoteListMarkdownOptions

type NoteListMarkdownOptions struct {
	Title           string
	EmptyMessage    string
	IncludeInternal bool
	Hints           []string
}

NoteListMarkdownOptions configures shared note list rendering.

type NoteMarkdown

type NoteMarkdown struct {
	ID         int64
	Body       string
	Author     string
	CreatedAt  string
	System     bool
	Internal   bool
	Resolvable bool
	Resolved   bool
	ResolvedBy string
}

NoteMarkdown carries common fields rendered by issue, merge request, and snippet note tools.

func NewNoteMarkdown

func NewNoteMarkdown(id int64, body, author, createdAt string, flags NoteMarkdownFlags, resolvedBy string) NoteMarkdown

NewNoteMarkdown builds a shared Markdown view model for GitLab notes.

func NoteMarkdowns

func NoteMarkdowns[T any](notes []T, convert func(T) NoteMarkdown) []NoteMarkdown

NoteMarkdowns maps package-specific note outputs to the shared note Markdown view model.

type NoteMarkdownFlags

type NoteMarkdownFlags struct {
	System     bool
	Internal   bool
	Resolvable bool
	Resolved   bool
}

NoteMarkdownFlags groups boolean note attributes for Markdown view-model construction.

type NoteMarkdownOptions

type NoteMarkdownOptions struct {
	Title             string
	IncludeInternal   bool
	IncludeResolvable bool
	Hints             []string
}

NoteMarkdownOptions configures shared note detail rendering.

type NoteOutput

type NoteOutput struct {
	HintableOutput
	ID              int64               `json:"id"`
	Body            string              `json:"body"`
	Author          *NoteUserOutput     `json:"author,omitempty"`
	CreatedAt       string              `json:"created_at"`
	UpdatedAt       string              `json:"updated_at"`
	System          bool                `json:"system"`
	Internal        bool                `json:"internal"`
	Resolvable      bool                `json:"resolvable,omitempty"`
	Resolved        bool                `json:"resolved,omitempty"`
	ResolvedAt      string              `json:"resolved_at,omitempty"`
	ResolvedBy      *NoteUserOutput     `json:"resolved_by,omitempty"`
	NoteableType    string              `json:"noteable_type,omitempty"`
	NoteableID      int64               `json:"noteable_id,omitempty"`
	NoteableIID     int64               `json:"noteable_iid,omitempty"`
	CommitID        string              `json:"commit_id,omitempty"`
	Type            string              `json:"type,omitempty"`
	Position        *NotePositionOutput `json:"position,omitempty"`
	ProjectID       int64               `json:"project_id,omitempty"`
	Confidential    bool                `json:"confidential"`
	Imported        bool                `json:"imported"`
	ImportedFrom    string              `json:"imported_from,omitempty"`
	CommandsChanges map[string]any      `json:"commands_changes,omitempty"`
	Suggestions     []SuggestionOutput  `json:"suggestions,omitempty"`
}

NoteOutput mirrors every field of GitLab's Note entity as the REST Notes API sends it (notes on merge requests, issues, snippets, etc.): the fields client-go's Note models, and the ones only the captured response carries. It is the canonical shared output shape for standalone note tools (mrnotes, issuenotes). Field order and JSON tags are locked; any change is a breaking wire-format change.

`attachment`, `title`, `file_name` and `expires_at`, which client-go's Note still carries, are not here: GitLab's entity does not expose them, and the R-PATH type grain reported them as fields no operation sends.

func NoteOutputFromGitLab

func NoteOutputFromGitLab(n *gl.Note, extra NoteExtra) NoteOutput

NoteOutputFromGitLab converts a gl.Note and what the captured response adds to it into the canonical NoteOutput shape used by standalone note tools (mrnotes, issuenotes). Timestamps are formatted as RFC 3339 strings.

type NotePositionOutput

type NotePositionOutput struct {
	BaseSHA      string           `json:"base_sha,omitempty"`
	StartSHA     string           `json:"start_sha,omitempty"`
	HeadSHA      string           `json:"head_sha,omitempty"`
	PositionType string           `json:"position_type,omitempty"`
	NewPath      string           `json:"new_path,omitempty"`
	NewLine      int64            `json:"new_line,omitempty"`
	OldPath      string           `json:"old_path,omitempty"`
	OldLine      int64            `json:"old_line,omitempty"`
	LineRange    *LineRangeOutput `json:"line_range,omitempty"`
}

NotePositionOutput mirrors gl.NotePosition: the diff position of a note that is attached to a specific line of a file.

func NewNotePositionOutput

func NewNotePositionOutput(p *gl.NotePosition) *NotePositionOutput

NewNotePositionOutput converts a *gl.NotePosition into the position object, returning nil when the note has no diff position.

type NoteUserExtra

type NoteUserExtra struct {
	PublicEmail string `json:"public_email"`
	Locked      bool   `json:"locked"`
}

NoteUserExtra is what a note's author or resolver carries that client-go's NoteAuthor and NoteResolvedBy do not model, read from the captured response.

type NoteUserOutput

type NoteUserOutput = UserBasicOutput

NoteUserOutput mirrors GitLab's UserBasic entity, which is what a note's author and resolver are rendered with: the identity fields, the account state and lock, and the two URLs. It carries no email, since UserBasic exposes `public_email` and never `email`; client-go's NoteAuthor carries an `email` GitLab does not send, and `public_email` and `locked` it does not model, which is why the two shared values come from the captured response (see NoteUserExtra).

func NewNoteUserOutputFromAuthor

func NewNoteUserOutputFromAuthor(a gl.NoteAuthor, extra NoteUserExtra) *NoteUserOutput

NewNoteUserOutputFromAuthor converts a gl.NoteAuthor value into the additive author object. The author is always present on a note, so this returns a pointer to a populated value (never nil) to keep the canonical `author` key stable.

func NewNoteUserOutputFromResolvedBy

func NewNoteUserOutputFromResolvedBy(r gl.NoteResolvedBy, extra NoteUserExtra) *NoteUserOutput

NewNoteUserOutputFromResolvedBy converts a gl.NoteResolvedBy value into the resolved-by object, returning nil when no user has resolved the note (zero ID + empty username).

type OnceMap

type OnceMap[K comparable, V any] struct {
	// contains filtered or unexported fields
}

OnceMap memoizes one value per key and builds each value exactly once, however many callers ask for it at the same moment.

The caches this replaces were all written as load, build, store-if-absent, which is correct and wasteful in the one situation they exist for: the HTTP pool starting a server per credential, where every one of them misses the same cold key, every one builds a full copy of a registry shape, a manifest snapshot or a reflected schema, and all but one are dropped having cost what the survivor cost. The entry carries the sync.Once, so the callers that lose the race wait for the winner's build instead of racing it.

The zero value is ready to use. The mutex is held only while the entry is looked up, never while a value is built, so builds for different keys still run in parallel.

func (*OnceMap[K, V]) Load

func (m *OnceMap[K, V]) Load(key K, build func() V) V

Load returns the value memoized for key, calling build to produce it the first time the key is asked for. Concurrent callers for one key wait for that single build; callers for different keys build in parallel.

A build that returns a zero value is memoized like any other, so a deterministic failure is not retried on every call. A build that panics memoizes nothing: the panic reaches its own caller unchanged and the key is left to be built again, which is what the load, build, store-if-absent caches this replaced did. sync.Once does not do that by itself, since it marks itself done however its function ends, so the spent slot is dropped by the next caller to find it holding nothing. Without that the key would be poisoned for the life of the process: the zero value served to everyone, no rebuild, and OnceMap.Peek reporting that nobody ever built it.

func (*OnceMap[K, V]) Peek

func (m *OnceMap[K, V]) Peek(key K) (V, bool)

Peek returns the value memoized for key, and false when no build for that key has finished. It never builds one, which is what makes it the right question for a test asking whether a call populated the cache.

type PackageExtra

type PackageExtra struct {
	CreatorID        int64                  `json:"creator_id"`
	ConanPackageName string                 `json:"conan_package_name"`
	ProjectID        int64                  `json:"project_id"`
	ProjectPath      string                 `json:"project_path"`
	Versions         []PackageVersionOutput `json:"versions"`
}

PackageExtra is what GitLab's package entity sends that the package itself does not say: who published it, unconditionally; the Conan recipe's own name on a Conan package; the owning project's id and path, sent when the package is listed across a group; and the package's other versions, sent when one package is asked for rather than a page of them.

func CapturedPackages

func CapturedPackages(capture *gitlabclient.ResponseCapture, decoded int) ([]PackageExtra, error)

CapturedPackages reads them off the captured answer to a list of packages, one extra per package in order, the count held to what the SDK decoded.

type PackagePipelineOutput

type PackagePipelineOutput struct {
	ID        int64            `json:"id"`
	IID       int64            `json:"iid"`
	ProjectID int64            `json:"project_id"`
	SHA       string           `json:"sha"`
	Ref       string           `json:"ref"`
	Status    string           `json:"status"`
	Source    string           `json:"source"`
	CreatedAt *time.Time       `json:"created_at"`
	UpdatedAt *time.Time       `json:"updated_at"`
	WebURL    string           `json:"web_url"`
	User      *UserBasicOutput `json:"user"`
}

PackagePipelineOutput is the pipeline that built a package version, sent to a caller allowed to read it.

type PackageTagOutput

type PackageTagOutput struct {
	ID        int64      `json:"id"`
	PackageID int64      `json:"package_id"`
	Name      string     `json:"name"`
	CreatedAt *time.Time `json:"created_at"`
	UpdatedAt *time.Time `json:"updated_at"`
}

PackageTagOutput is one tag pointing at a package version.

type PackageVersionOutput

type PackageVersionOutput struct {
	ID        int64                  `json:"id"`
	Version   string                 `json:"version"`
	CreatedAt *time.Time             `json:"created_at"`
	Tags      []PackageTagOutput     `json:"tags"`
	Pipeline  *PackagePipelineOutput `json:"pipeline"`
}

PackageVersionOutput is one other version of the same package, with the tags pointing at it and the pipeline that built it.

type PagesCertificateExpirationOutput

type PagesCertificateExpirationOutput struct {
	Expired    bool       `json:"expired"`
	Expiration *time.Time `json:"expiration"`
}

PagesCertificateExpirationOutput is when a Pages domain's certificate stops being valid, and whether it already has.

type PagesDomainExtra

type PagesDomainExtra struct {
	CertificateExpiration *PagesCertificateExpirationOutput `json:"certificate_expiration"`
}

PagesDomainExtra is that object on the domain, which GitLab exposes only on a domain that has a certificate at all.

func CapturedPagesDomain

func CapturedPagesDomain(capture *gitlabclient.ResponseCapture) (PagesDomainExtra, error)

CapturedPagesDomain reads it off the captured answer to a request for one domain.

func CapturedPagesDomains

func CapturedPagesDomains(capture *gitlabclient.ResponseCapture, decoded int) ([]PagesDomainExtra, error)

CapturedPagesDomains reads the same off a list answer, one extra per domain in order, the count held to what the SDK decoded.

type PaginationInput

type PaginationInput struct {
	Page    int `` /* 156-byte string literal not displayed */
	PerPage int `` /* 156-byte string literal not displayed */
}

PaginationInput holds common pagination query parameters for list endpoints. Constraints (page>=1, per_page in [1,100]) are also enforced at the JSON Schema level by EnrichPaginationConstraints so LLM clients see the bounds directly in tools/list responses.

type PaginationOutput

type PaginationOutput struct {
	Page       int64 `json:"page"`
	PerPage    int64 `json:"per_page"`
	TotalItems int64 `json:"total_items"`
	TotalPages int64 `json:"total_pages"`
	NextPage   int64 `json:"next_page"`
	PrevPage   int64 `json:"prev_page"`
	HasMore    bool  `json:"has_more"`
}

PaginationOutput holds pagination metadata extracted from GitLab API responses. Fields map to GitLab's X-Page, X-Per-Page, X-Total, X-Total-Pages, X-Next-Page, X-Prev-Page headers. HasMore is a derived convenience flag (NextPage > 0) so LLM clients can decide whether to paginate without inspecting NextPage.

func PaginationFromResponse

func PaginationFromResponse(resp *gl.Response) PaginationOutput

PaginationFromResponse extracts pagination metadata from a GitLab API response.

type ParamAliasExplanation

type ParamAliasExplanation struct {
	Alias     string `json:"alias"`
	Canonical string `json:"canonical"`
	Source    string `json:"source"`
	Notes     string `json:"notes,omitempty"`
}

ParamAliasExplanation describes a compatibility parameter normalization. It intentionally records parameter names only, never parameter values.

func NormalizeParamAliasesForSchemaWithExplanation

func NormalizeParamAliasesForSchemaWithExplanation(params, schema map[string]any) (map[string]any, []ParamAliasExplanation)

NormalizeParamAliasesForSchemaWithExplanation returns the normalized params and name-only metadata describing compatibility aliases that were applied.

type ParamValidationError

type ParamValidationError struct {
	Err error
}

ParamValidationError marks parameter decoding failures that should be surfaced as recoverable tool errors instead of protocol errors.

func (*ParamValidationError) Error

func (e *ParamValidationError) Error() string

Error returns the underlying validation error message.

func (*ParamValidationError) Unwrap

func (e *ParamValidationError) Unwrap() error

Unwrap returns the underlying validation error.

type ParameterAliasSpec

type ParameterAliasSpec struct {
	Alias          string
	Target         string
	Source         string
	Searchable     bool
	Deprecated     bool
	RemovalVersion string
	Reason         string
}

ParameterAliasSpec describes a compatibility alias for one action parameter.

type ParameterGuidance

type ParameterGuidance struct {
	SemanticRole     string   `json:"semantic_role,omitempty"`
	ValueSource      string   `json:"value_source,omitempty"`
	CommonConfusions []string `json:"common_confusions,omitempty"`
	ExampleBinding   string   `json:"example_binding,omitempty"`
}

ParameterGuidance carries compact model-facing hints for parameters that are easy to confuse across similar GitLab actions.

func DiscussionIDParamGuidance

func DiscussionIDParamGuidance(valueSource string) ParameterGuidance

DiscussionIDParamGuidance returns the canonical parameter guidance for the discussion_id parameter used by discussion-scoped actions across the discussion domains (MR, issue, snippet, commit, epic). valueSource is the domain-specific description of where the thread id comes from; the semantic role, example binding, and thread-vs-note confusion note are identical for every domain.

func DiscussionNoteIDParamGuidance

func DiscussionNoteIDParamGuidance(valueSource string) ParameterGuidance

DiscussionNoteIDParamGuidance returns the canonical parameter guidance for the note_id parameter of discussion note update/delete actions. valueSource is the domain-specific description of where the note id comes from; the semantic role, example binding, and note-vs-thread confusion note are identical for every discussion domain.

type PersonalTokenOutput

type PersonalTokenOutput struct {
	ID             int64                      `json:"id"`
	Name           string                     `json:"name"`
	Active         bool                       `json:"active"`
	Token          string                     `json:"token,omitempty"`
	Scopes         []string                   `json:"scopes"`
	Granular       bool                       `json:"granular"`
	GranularScopes []TokenGranularScopeOutput `json:"granular_scopes,omitempty"`
	Revoked        bool                       `json:"revoked"`
	Description    string                     `json:"description,omitempty"`
	UserID         int64                      `json:"user_id"`
	CreatedAt      string                     `json:"created_at,omitempty"`
	ExpiresAt      string                     `json:"expires_at,omitempty"`
	LastUsedAt     string                     `json:"last_used_at,omitempty"`
	LastUsedIPs    []string                   `json:"last_used_ips,omitempty"`
}

PersonalTokenOutput mirrors gl.PersonalAccessToken as surfaced by the impersonation-token and current-user PAT tools: identity, scopes, and RFC3339/ISO-date lifecycle timestamps. The token value is only present on creation responses. It also carries what lib/api/entities/personal_access_token.rb sends and the SDK struct does not model, read from the captured response (ADR-0021): whether the token is granular, its granular scopes when it is and the endpoint asked for them, and the addresses it was last used from while the instance exposes them.

func NewPersonalTokenOutput

func NewPersonalTokenOutput(t *gl.PersonalAccessToken, extra TokenExtra) PersonalTokenOutput

NewPersonalTokenOutput converts a gl.PersonalAccessToken into the shared output shape, formatting created/last-used as RFC3339 and expiry as an ISO date, and takes the fields the capture read beside the SDK.

type PipelineDetailedStatusIllustrationOutput

type PipelineDetailedStatusIllustrationOutput struct {
	Image string `json:"image"`
}

PipelineDetailedStatusIllustrationOutput mirrors gl.DetailedStatus.Illustration (the icon image sub-object).

func NewPipelineDetailedStatusIllustrationOutput

func NewPipelineDetailedStatusIllustrationOutput(image string) *PipelineDetailedStatusIllustrationOutput

NewPipelineDetailedStatusIllustrationOutput is reserved for SDK versions that surface Illustration as a pointer; currently the SDK exposes it as a value type, so NewPipelineDetailedStatusOutput handles the conversion inline. Kept for forward compatibility.

type PipelineDetailedStatusOutput

type PipelineDetailedStatusOutput struct {
	Icon         string                                    `json:"icon"`
	Text         string                                    `json:"text"`
	Label        string                                    `json:"label"`
	Group        string                                    `json:"group"`
	Tooltip      string                                    `json:"tooltip"`
	HasDetails   bool                                      `json:"has_details"`
	DetailsPath  string                                    `json:"details_path"`
	Illustration *PipelineDetailedStatusIllustrationOutput `json:"illustration,omitempty"`
	Favicon      string                                    `json:"favicon"`
}

PipelineDetailedStatusOutput mirrors gl.DetailedStatus (the icon / text / label / group / tooltip / details_path / illustration / favicon object that GitLab returns inside gl.Pipeline.DetailedStatus).

func NewPipelineDetailedStatusOutput

func NewPipelineDetailedStatusOutput(s *gl.DetailedStatus) *PipelineDetailedStatusOutput

NewPipelineDetailedStatusOutput converts a *gl.DetailedStatus into the canonical-key detailed-status object, returning nil when the SDK value is nil. The SDK's Illustration field is a non-pointer gl.DetailedStatusIllustration value; we surface the nested image only when populated, keeping the canonical nil-on-empty contract.

type PipelineExtra

type PipelineExtra struct {
	Archived bool `json:"archived"`
}

PipelineExtra is what lib/api/entities/ci/pipeline.rb sends on a pipeline that client-go's Pipeline does not carry: whether it is archived, sent on every pipeline rendered whole.

func CapturedPipeline

func CapturedPipeline(capture *gitlabclient.ResponseCapture) (PipelineExtra, error)

CapturedPipeline reads, off the captured answer to a request for one pipeline, the field client-go's Pipeline does not model.

type PipelineInfoOutput

type PipelineInfoOutput struct {
	ID        int64  `json:"id"`
	IID       int64  `json:"iid"`
	ProjectID int64  `json:"project_id"`
	Status    string `json:"status"`
	Source    string `json:"source"`
	Ref       string `json:"ref"`
	SHA       string `json:"sha"`
	Name      string `json:"name"`
	WebURL    string `json:"web_url"`
	UpdatedAt string `json:"updated_at,omitempty"`
	CreatedAt string `json:"created_at,omitempty"`
}

PipelineInfoOutput mirrors gl.PipelineInfo (the compact pipeline summary surfaced on a merge request's head_pipeline / pipeline fields).

func NewPipelineInfoOutput

func NewPipelineInfoOutput(p *gl.PipelineInfo) *PipelineInfoOutput

NewPipelineInfoOutput converts a gl.PipelineInfo pointer into the canonical-key pipeline info object, returning nil for a nil source.

type PipelineOutput

type PipelineOutput struct {
	ID             int64                         `json:"id"`
	IID            int64                         `json:"iid"`
	ProjectID      int64                         `json:"project_id"`
	Status         string                        `json:"status"`
	Source         string                        `json:"source"`
	Ref            string                        `json:"ref"`
	Name           string                        `json:"name"`
	SHA            string                        `json:"sha"`
	BeforeSHA      string                        `json:"before_sha"`
	Tag            bool                          `json:"tag"`
	YamlErrors     string                        `json:"yaml_errors"`
	User           *BasicUserOutput              `json:"user,omitempty"`
	UpdatedAt      string                        `json:"updated_at,omitempty"`
	CreatedAt      string                        `json:"created_at,omitempty"`
	StartedAt      string                        `json:"started_at,omitempty"`
	FinishedAt     string                        `json:"finished_at,omitempty"`
	CommittedAt    string                        `json:"committed_at,omitempty"`
	Duration       int64                         `json:"duration"`
	QueuedDuration int64                         `json:"queued_duration"`
	Coverage       string                        `json:"coverage"`
	WebURL         string                        `json:"web_url"`
	DetailedStatus *PipelineDetailedStatusOutput `json:"detailed_status,omitempty"`
}

PipelineOutput mirrors gl.Pipeline (the per-pipeline summary object returned by head_pipeline, merge_train.pipeline, deployment.merge_request .head_pipeline, etc.). It surfaces every field of the SDK struct that the documented GitLab API returns (1:1 audit policy) plus the nested user and detailed_status sub-objects on their canonical keys.

func NewPipelineOutput

func NewPipelineOutput(p *gl.Pipeline) *PipelineOutput

NewPipelineOutput converts a *gl.Pipeline into the canonical-key pipeline object, returning nil when the SDK value is nil. Timestamps are surfaced as RFC 3339 strings via toolutil.FormatTimePtr.

type PipelineTriggerExtra

type PipelineTriggerExtra struct {
	ExpiresAt *time.Time `json:"expires_at"`
}

PipelineTriggerExtra is when a trigger token stops working, exposed under no condition and null on a token that never expires.

func CapturedPipelineTrigger

func CapturedPipelineTrigger(capture *gitlabclient.ResponseCapture) (PipelineTriggerExtra, error)

CapturedPipelineTrigger reads it off the captured answer to a request for one trigger.

func CapturedPipelineTriggers

func CapturedPipelineTriggers(capture *gitlabclient.ResponseCapture, decoded int) ([]PipelineTriggerExtra, error)

CapturedPipelineTriggers reads the same off a list answer, one extra per trigger in order, the count held to what the SDK decoded.

type ProgressReader

type ProgressReader struct {
	// contains filtered or unexported fields
}

ProgressReader wraps an io.Reader and reports progress to an MCP progress tracker as bytes are read. Safe to use with a zero-value/inactive tracker.

func NewProgressReader

func NewProgressReader(ctx context.Context, r io.Reader, total int64, tracker progress.Tracker) *ProgressReader

NewProgressReader creates a ProgressReader that reports upload progress. If the tracker is inactive, the wrapper still works but skips notifications.

func (*ProgressReader) BytesRead

func (pr *ProgressReader) BytesRead() int64

BytesRead returns the total number of bytes read so far.

func (*ProgressReader) Read

func (pr *ProgressReader) Read(p []byte) (int, error)

Read implements io.Reader. It reads from the inner reader and periodically sends progress notifications via the MCP tracker.

type ProgressWriter

type ProgressWriter struct {
	// contains filtered or unexported fields
}

ProgressWriter wraps an io.Writer and reports progress to an MCP progress tracker as bytes are written (used for downloads to disk).

func NewProgressWriter

func NewProgressWriter(ctx context.Context, w io.Writer, total int64, tracker progress.Tracker) *ProgressWriter

NewProgressWriter creates a ProgressWriter that reports download progress.

func (*ProgressWriter) BytesWritten

func (pw *ProgressWriter) BytesWritten() int64

BytesWritten returns the total number of bytes written so far.

func (*ProgressWriter) Write

func (pw *ProgressWriter) Write(p []byte) (int, error)

Write implements io.Writer. It writes to the inner writer and periodically sends progress notifications via the MCP tracker.

type ProtectedBranchExtra

type ProtectedBranchExtra struct {
	Inherited bool `json:"inherited"`
}

ProtectedBranchExtra is whether a protected branch rule was inherited from the group rather than declared on the project, exposed under no condition.

func CapturedProtectedBranch

func CapturedProtectedBranch(capture *gitlabclient.ResponseCapture) (ProtectedBranchExtra, error)

CapturedProtectedBranch reads it off the captured answer to a request for one protected branch.

func CapturedProtectedBranches

func CapturedProtectedBranches(capture *gitlabclient.ResponseCapture, decoded int) ([]ProtectedBranchExtra, error)

CapturedProtectedBranches reads the same off a list answer, one extra per branch in order, the count held to what the SDK decoded.

type RateLimiter

type RateLimiter struct {
	// contains filtered or unexported fields
}

RateLimiter enforces a token-bucket rate limit on the methods that cost a deployment something: those reaching GitLab with the caller's credential, plus `completion/complete` and `tools/list`, each on the bucket AttachRateLimit describes. A zero RPS disables the limiter (the constructor returns nil and the resulting middleware is a no-op).

Limits are advisory; the primary defense remains GitLab's own per-token rate limits. The local limiter exists to soften bursts (typical LLM retry-loop with a flaky tool can fire dozens of identical calls per second) and to give operators a single knob they can tighten when they see 429s in practice. HTTP mode enables it by default (10 rps, burst 40); stdio leaves it off, since there the bucket would be global to one client.

The limiter shares a single bucket across the server. In HTTP mode each server instance from the pool gets its own RateLimiter, so the limit is effectively per token and GitLab URL. In stdio mode the bucket is global to the single process.

rate.Limiter is safe for concurrent use by design, so RateLimiter does not need additional synchronization of its own.

func NewRateLimiter

func NewRateLimiter(rps float64, burst int) *RateLimiter

NewRateLimiter builds a RateLimiter with the given rate (requests per second) and burst (maximum concurrent tokens in the bucket). Returns nil if rps <= 0, which the middleware treats as "disabled". Burst is clamped to a minimum of 1 when rps > 0 to avoid an unusable zero-burst limiter.

type ReferencesOutput

type ReferencesOutput struct {
	Short    string `json:"short"`
	Relative string `json:"relative"`
	Full     string `json:"full"`
}

ReferencesOutput mirrors gl.References (the short / relative / full reference triplet surfaced inside issue and MR resources).

func NewReferencesOutput

func NewReferencesOutput(r *gl.IssueReferences) *ReferencesOutput

NewReferencesOutput converts a gl.IssueReferences value into the canonical-key references object, returning nil when the source is the zero value.

type RegistryRepositoryExtra

type RegistryRepositoryExtra struct {
	Size          int64  `json:"size"`
	DeleteAPIPath string `json:"delete_api_path"`
}

RegistryRepositoryExtra is what GitLab's container registry repository entity sends beside the name and the path: the size, when the caller asked for it, and the path a caller allowed to administer images deletes through.

func CapturedRegistryRepositories

func CapturedRegistryRepositories(capture *gitlabclient.ResponseCapture, decoded int) ([]RegistryRepositoryExtra, error)

CapturedRegistryRepositories reads the same off a list answer, one extra per repository in order, the count held to what the SDK decoded.

func CapturedRegistryRepository

func CapturedRegistryRepository(capture *gitlabclient.ResponseCapture) (RegistryRepositoryExtra, error)

CapturedRegistryRepository reads them off the captured answer to a request for one repository.

type ResourceMilestoneEventExtra

type ResourceMilestoneEventExtra struct {
	State string `json:"state"`
}

ResourceMilestoneEventExtra is the issue or merge request's own state at the moment the milestone changed, which the milestone event entity exposes under no condition.

func CapturedResourceMilestoneEvent

func CapturedResourceMilestoneEvent(capture *gitlabclient.ResponseCapture) (ResourceMilestoneEventExtra, error)

CapturedResourceMilestoneEvent reads it off the captured answer to a request for one event.

func CapturedResourceMilestoneEvents

func CapturedResourceMilestoneEvents(capture *gitlabclient.ResponseCapture, decoded int) ([]ResourceMilestoneEventExtra, error)

CapturedResourceMilestoneEvents reads the same off a list answer, one extra per event in order, the count held to what the SDK decoded.

type ResourceStateEventExtra

type ResourceStateEventExtra struct {
	SourceCommit         string `json:"source_commit"`
	SourceMergeRequestID int64  `json:"source_merge_request_id"`
}

ResourceStateEventExtra is what GitLab's resource state event entity sends that client-go's StateEvent does not carry: the commit that closed the issue, and the merge request that did, each empty when something else did.

func CapturedResourceStateEvent

func CapturedResourceStateEvent(capture *gitlabclient.ResponseCapture) (ResourceStateEventExtra, error)

CapturedResourceStateEvent reads them off the captured answer to a request for one event.

func CapturedResourceStateEvents

func CapturedResourceStateEvents(capture *gitlabclient.ResponseCapture, decoded int) ([]ResourceStateEventExtra, error)

CapturedResourceStateEvents reads the same off a list answer, one extra per event in order, the count held to what the SDK decoded.

type ResourceTokenExtra

type ResourceTokenExtra struct {
	TokenExtra
	ResourceType string `json:"resource_type"`
	ResourceID   int64  `json:"resource_id"`
}

ResourceTokenExtra is what lib/api/entities/resource_access_token.rb sends beyond TokenExtra on a project or group access token: which kind of resource the token belongs to and its id, the second absent when the bot user has no namespace.

func CapturedResourceToken

func CapturedResourceToken(capture *gitlabclient.ResponseCapture) (ResourceTokenExtra, error)

CapturedResourceToken reads, off the captured answer to a request for one project or group access token, the fields the SDK's resource access token does not model.

func CapturedResourceTokens

func CapturedResourceTokens(capture *gitlabclient.ResponseCapture, decoded int) ([]ResourceTokenExtra, error)

CapturedResourceTokens reads the same off a list answer, one extra per token in order, the count held to what the SDK decoded.

type RunnerExtra

type RunnerExtra struct {
	CreatedAt          *time.Time       `json:"created_at"`
	CreatedBy          *UserBasicOutput `json:"created_by"`
	JobExecutionStatus string           `json:"job_execution_status"`
}

RunnerExtra is what lib/api/entities/ci/runner.rb sends on a runner that client-go's Runner and RunnerDetails do not carry: when it was created, who created it, sent to a caller allowed to read that user, and the job execution status.

func CapturedRunner

func CapturedRunner(capture *gitlabclient.ResponseCapture) (RunnerExtra, error)

CapturedRunner reads, off the captured answer to a request for one runner, the fields client-go's runner structs do not model.

func CapturedRunners

func CapturedRunners(capture *gitlabclient.ResponseCapture, decoded int) ([]RunnerExtra, error)

CapturedRunners reads the same off a list answer, one extra per runner in order, the count held to what the SDK decoded.

type RunnerManagerExtra

type RunnerManagerExtra struct {
	JobExecutionStatus string `json:"job_execution_status"`
}

RunnerManagerExtra is what the manager is doing now, exposed under no condition on the runner manager entity. It is the same key RunnerExtra carries on the runner itself and a different object: a runner's status is the aggregate of its managers'.

func CapturedRunnerManagers

func CapturedRunnerManagers(capture *gitlabclient.ResponseCapture, decoded int) ([]RunnerManagerExtra, error)

CapturedRunnerManagers reads it off the captured answer to a list of managers, one extra per manager in order, the count held to what the SDK decoded.

type SAMLIdentityOutput

type SAMLIdentityOutput struct {
	ExternUID      string `json:"extern_uid"`
	Provider       string `json:"provider"`
	SAMLProviderID int64  `json:"saml_provider_id"`
}

SAMLIdentityOutput mirrors gl.GroupMemberSAMLIdentity (the group_saml_identity object).

func NewSAMLIdentityOutput

func NewSAMLIdentityOutput(s *gl.GroupMemberSAMLIdentity) *SAMLIdentityOutput

NewSAMLIdentityOutput mirrors a gl.GroupMemberSAMLIdentity into the shared output shape, returning nil when the SDK value is nil.

type SCIMIdentityExtra

type SCIMIdentityExtra struct {
	ExternUID string `json:"extern_uid"`
}

SCIMIdentityExtra is the identifier the SCIM provider knows a user by, which GitLab's identity detail entity exposes under no condition.

func CapturedSCIMIdentities

func CapturedSCIMIdentities(capture *gitlabclient.ResponseCapture, decoded int) ([]SCIMIdentityExtra, error)

CapturedSCIMIdentities reads the same off a list answer, one extra per identity in order, the count held to what the SDK decoded.

func CapturedSCIMIdentity

func CapturedSCIMIdentity(capture *gitlabclient.ResponseCapture) (SCIMIdentityExtra, error)

CapturedSCIMIdentity reads it off the captured answer to a request for one identity.

type SCIMIdentityOutput

type SCIMIdentityOutput struct {
	ExternUID string `json:"extern_uid"`
	GroupID   int64  `json:"group_id"`
	Active    bool   `json:"active"`
}

SCIMIdentityOutput mirrors the group_scim_identity object, ee/lib/api/entities/scim_identity.rb, which client-go does not model: the SCIM identity a member holds in an SSO-enabled group, sent to the group's owners.

type SafeModePreview

type SafeModePreview struct {
	Status string          `json:"status"`
	Mode   string          `json:"mode"`
	Tool   string          `json:"tool"`
	Params json.RawMessage `json:"params"`
	Hint   string          `json:"hint"`
}

SafeModePreview is the structured response returned when a mutating operation is intercepted by Safe Mode. Status is always "blocked", Mode is "safe", Tool names the intercepted tool or canonical action, Params mirrors the would-be call arguments, and Hint tells the operator how to disable safe mode.

func NewSafeModePreview

func NewSafeModePreview(name string, params any) SafeModePreview

NewSafeModePreview builds a preview for name, marshaling params defensively: when params cannot be marshaled the preview still reports the blocked operation with a null params payload rather than failing the call.

type SecureFileExtra

type SecureFileExtra struct {
	FileExtension string `json:"file_extension"`
}

SecureFileExtra is a secure file's extension, exposed under no condition.

func CapturedSecureFile

func CapturedSecureFile(capture *gitlabclient.ResponseCapture) (SecureFileExtra, error)

CapturedSecureFile reads it off the captured answer to a request for one secure file.

func CapturedSecureFiles

func CapturedSecureFiles(capture *gitlabclient.ResponseCapture, decoded int) ([]SecureFileExtra, error)

CapturedSecureFiles reads the same off a list answer, one extra per file in order, the count held to what the SDK decoded.

type ServiceAccountExtra

type ServiceAccountExtra struct {
	PublicEmail      string `json:"public_email"`
	UnconfirmedEmail string `json:"unconfirmed_email"`
}

ServiceAccountExtra is what GitLab's service account entity sends beside the name and username: the public email always, and the unconfirmed one while a change of address is waiting to be confirmed.

Both keys are read here for the group endpoint, whose GroupServiceAccount carries neither. The project endpoint's ServiceAccount already models the unconfirmed address, so that package takes it from the SDK and only the public email from here: the same GitLab entity, modeled twice upstream and unevenly.

func CapturedServiceAccount

func CapturedServiceAccount(capture *gitlabclient.ResponseCapture) (ServiceAccountExtra, error)

CapturedServiceAccount reads them off the captured answer to a request for one service account.

func CapturedServiceAccounts

func CapturedServiceAccounts(capture *gitlabclient.ResponseCapture, decoded int) ([]ServiceAccountExtra, error)

CapturedServiceAccounts reads the same off a list answer, one extra per account in order, the count held to what the SDK decoded.

type SnippetExtra

type SnippetExtra struct {
	Imported      bool   `json:"imported"`
	ImportedFrom  string `json:"imported_from"`
	SSHURLToRepo  string `json:"ssh_url_to_repo"`
	HTTPURLToRepo string `json:"http_url_to_repo"`
}

SnippetExtra is what GitLab's snippet entity sends that the snippet itself does not say: whether it arrived with an import rather than being written here and which platform it came from, both unconditional, and the two clone URLs of its repository, sent once that repository exists.

func CapturedSnippet

func CapturedSnippet(capture *gitlabclient.ResponseCapture) (SnippetExtra, error)

CapturedSnippet reads them off the captured answer to a request for one snippet.

func CapturedSnippets

func CapturedSnippets(capture *gitlabclient.ResponseCapture, decoded int) ([]SnippetExtra, error)

CapturedSnippets reads the same off a list answer, one extra per snippet in order, the count held to what the SDK decoded.

type StorageMoveEntityMarkdown

type StorageMoveEntityMarkdown struct {
	Label string
	Name  string
	URL   string
	ID    int64
}

StorageMoveEntityMarkdown carries the optional GitLab resource associated with a repository storage move.

func NewStorageMoveEntityMarkdown

func NewStorageMoveEntityMarkdown(label, name, url string, id int64) *StorageMoveEntityMarkdown

NewStorageMoveEntityMarkdown builds the optional entity view model for a repository storage move.

type StorageMoveExtra

type StorageMoveExtra struct {
	ErrorMessage string `json:"error_message"`
}

StorageMoveExtra is why a repository storage move failed, exposed under no condition by the group, project and snippet storage move entities alike and empty on a move that did not fail.

func CapturedStorageMove

func CapturedStorageMove(capture *gitlabclient.ResponseCapture) (StorageMoveExtra, error)

CapturedStorageMove reads it off the captured answer to a request for one storage move.

func CapturedStorageMoves

func CapturedStorageMoves(capture *gitlabclient.ResponseCapture, decoded int) ([]StorageMoveExtra, error)

CapturedStorageMoves reads the same off a list answer, one extra per move in order, the count held to what the SDK decoded.

type StorageMoveListMarkdownOptions

type StorageMoveListMarkdownOptions struct {
	Title        string
	EmptyMessage string
	EntityColumn string
	Pagination   PaginationOutput
}

StorageMoveListMarkdownOptions configures the shared storage move list renderer.

type StorageMoveMarkdown

type StorageMoveMarkdown struct {
	ID                     int64
	State                  string
	SourceStorageName      string
	DestinationStorageName string
	CreatedAt              time.Time
	Entity                 *StorageMoveEntityMarkdown
}

StorageMoveMarkdown carries the common fields rendered by repository storage move tools at group, snippet, and other resource scopes.

func NewStorageMoveMarkdown

func NewStorageMoveMarkdown(id int64, state, sourceStorageName, destinationStorageName string, createdAt time.Time, entity *StorageMoveEntityMarkdown) StorageMoveMarkdown

NewStorageMoveMarkdown builds a shared repository storage move Markdown view model without forcing tool packages to duplicate composite literals.

func StorageMoveMarkdowns

func StorageMoveMarkdowns[T any](moves []T, convert func(T) StorageMoveMarkdown) []StorageMoveMarkdown

StorageMoveMarkdowns maps package-specific storage move outputs to the shared Markdown view model.

type StringOrInt

type StringOrInt string

StringOrInt is a string type that accepts both JSON strings and JSON numbers during unmarshalling. It always stores the value as a string internally. This is needed because LLMs frequently send numeric IDs (e.g. 405) as JSON numbers rather than strings, even when the schema declares "type": "string".

func (StringOrInt) Int64

func (s StringOrInt) Int64() (int64, error)

Int64 parses the stored string as a base-10 integer and returns it. Returns 0 and an error if the value is empty or not a valid integer.

func (StringOrInt) MarshalJSON

func (s StringOrInt) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler to always output a JSON string.

func (StringOrInt) String

func (s StringOrInt) String() string

String returns the underlying string value.

func (*StringOrInt) UnmarshalJSON

func (s *StringOrInt) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler to accept both JSON strings (e.g. "405", "group/project") and JSON numbers (e.g. 405, 42.0).

type SuggestionOutput

type SuggestionOutput struct {
	ID          int64  `json:"id"`
	FromLine    int64  `json:"from_line"`
	ToLine      int64  `json:"to_line"`
	Appliable   bool   `json:"appliable"`
	Applied     bool   `json:"applied"`
	FromContent string `json:"from_content"`
	ToContent   string `json:"to_content"`
}

SuggestionOutput mirrors GitLab's Suggestion entity, the code suggestion a merge request diff note carries.

type SurfaceToolRegisterOptions

type SurfaceToolRegisterOptions struct {
	Description  string
	Icons        []mcp.Icon
	FormatResult FormatResultFunc
}

SurfaceToolRegisterOptions controls how an ActionSpec is exposed as a standalone visible MCP tool.

type SystemHookExtra

type SystemHookExtra struct {
	PushEventsBranchFilter string             `json:"push_events_branch_filter"`
	BranchFilterStrategy   string             `json:"branch_filter_strategy"`
	AlertStatus            string             `json:"alert_status"`
	DisabledUntil          *time.Time         `json:"disabled_until"`
	CustomWebhookTemplate  string             `json:"custom_webhook_template"`
	CustomHeaders          []HookHeaderOutput `json:"custom_headers"`
	OrganizationID         int64              `json:"organization_id"`
}

SystemHookExtra is what GitLab's hook entity sends beside the event flags: which branches a push triggers on and how that filter is read, whether the hook has been disabled after failing and until when, the template its payload is rendered from, the custom headers configured on it, and the organization a system hook belongs to.

All of them are exposed unconditionally except the headers, which a caller can ask to be left out, and the organization, which only a system hook has.

func CapturedSystemHook

func CapturedSystemHook(capture *gitlabclient.ResponseCapture) (SystemHookExtra, error)

CapturedSystemHook reads them off the captured answer to a request for one hook.

func CapturedSystemHooks

func CapturedSystemHooks(capture *gitlabclient.ResponseCapture, decoded int) ([]SystemHookExtra, error)

CapturedSystemHooks reads the same off a list answer, one extra per hook in order, the count held to what the SDK decoded.

type TaskCompletionStatusOutput

type TaskCompletionStatusOutput struct {
	Count          int64 `json:"count"`
	CompletedCount int64 `json:"completed_count"`
}

TaskCompletionStatusOutput mirrors gl.TaskCompletionStatus (the task-count object surfaced on merge requests).

func NewTaskCompletionStatusOutput

func NewTaskCompletionStatusOutput(t *gl.TasksCompletionStatus) *TaskCompletionStatusOutput

NewTaskCompletionStatusOutput converts a gl.TasksCompletionStatus pointer into the canonical-key task status object, returning nil for a nil source.

type TemplateAttributeListMarkdownItem

type TemplateAttributeListMarkdownItem struct {
	Key       string
	Name      string
	Attribute string
}

TemplateAttributeListMarkdownItem carries common list-row fields for template-style Markdown tables with a third attribute column.

type TemplateAttributeListMarkdownOptions

type TemplateAttributeListMarkdownOptions struct {
	Title           string
	EmptyMessage    string
	AttributeHeader string
	Pagination      PaginationOutput
	Hints           []string
}

TemplateAttributeListMarkdownOptions configures template-style list rendering for tables that include a third attribute column.

type TemplateDetailMarkdown

type TemplateDetailMarkdown struct {
	Title          string
	Key            string
	Nickname       string
	Popular        bool
	Description    string
	Permissions    []string
	Conditions     []string
	Limitations    []string
	Content        string
	ContentHeading string
	PlainFields    bool
	Hints          []string
}

TemplateDetailMarkdown carries common fields for template-style detail pages.

type TemplateListMarkdownOptions

type TemplateListMarkdownOptions struct {
	Title        string
	EmptyMessage string
	Hints        []string
}

TemplateListMarkdownOptions configures shared template list rendering.

type TemplateMarkdown

type TemplateMarkdown struct {
	Key  string `json:"key"`
	Name string `json:"name"`
}

TemplateMarkdown carries common fields rendered by template list tools.

func NewTemplateMarkdown

func NewTemplateMarkdown(key, name string) TemplateMarkdown

NewTemplateMarkdown builds a shared Markdown view model for GitLab template list entries.

func TemplateMarkdowns

func TemplateMarkdowns[T any](templates []T, convert func(T) TemplateMarkdown) []TemplateMarkdown

TemplateMarkdowns maps package-specific template outputs to the shared template Markdown view model.

type TemplateRenderer

type TemplateRenderer struct {
	ListTitle    string
	EmptyMessage string
	ListHint     string
	DetailTitle  string
	Language     string
	DetailHint   string
}

TemplateRenderer stores the stable labels and hints for a GitLab template family so package formatters can avoid repeating identical rendering glue.

func NewTemplateRenderer

func NewTemplateRenderer(listTitle, emptyMessage, listHint, detailTitle, language, detailHint string) TemplateRenderer

NewTemplateRenderer builds a renderer for a GitLab template family.

func (TemplateRenderer) FormatContent

func (r TemplateRenderer) FormatContent(name, content string) string

FormatContent renders a single GitLab template body with the renderer configuration.

func (TemplateRenderer) FormatList

func (r TemplateRenderer) FormatList(templates []TemplateMarkdown, pagination PaginationOutput) string

FormatList renders a GitLab template list with the renderer configuration.

type TimeStatsOutput

type TimeStatsOutput struct {
	HumanTimeEstimate   string `json:"human_time_estimate"`
	HumanTotalTimeSpent string `json:"human_total_time_spent"`
	TimeEstimate        int64  `json:"time_estimate"`
	TotalTimeSpent      int64  `json:"total_time_spent"`
}

TimeStatsOutput mirrors gl.TimeStats (the time-tracking sub-object on merge requests and issues). It is the pure nested form — no next_steps — used when time_stats appears as a sub-field of another output type rather than as the standalone return value of a time-tracking handler. The standalone handlers (SetTimeEstimate, AddSpentTime, etc.) compose this with HintableOutput to add next_steps at the top level of their response.

func NewTimeStatsOutput

func NewTimeStatsOutput(t *gl.TimeStats) *TimeStatsOutput

NewTimeStatsOutput converts a gl.TimeStats pointer to the canonical pure TimeStatsOutput, returning nil when the source is nil.

type TodoExtra

type TodoExtra struct {
	UpdatedAt *time.Time            `json:"updated_at"`
	Group     *NamespaceBasicOutput `json:"group"`
}

TodoExtra is what GitLab's todo entity sends that client-go's Todo does not carry: when the todo last changed, and the group it belongs to, which is present only on a todo raised in a group rather than a project.

func CapturedTodo

func CapturedTodo(capture *gitlabclient.ResponseCapture) (TodoExtra, error)

CapturedTodo reads them off the captured answer to a request that returned one todo.

func CapturedTodos

func CapturedTodos(capture *gitlabclient.ResponseCapture, decoded int) ([]TodoExtra, error)

CapturedTodos reads the same off a list answer, one extra per todo in order, the count held to what the SDK decoded.

type TokenExtra

type TokenExtra struct {
	Granular       bool                       `json:"granular"`
	GranularScopes []TokenGranularScopeOutput `json:"granular_scopes"`
	LastUsedIPs    []string                   `json:"last_used_ips"`
}

TokenExtra is what lib/api/entities/personal_access_token.rb and the entities inheriting it send on a token that client-go's PersonalAccessToken does not carry: granular on every token, granular_scopes when the token is granular and the endpoint asked for them, and last_used_ips while the instance has the feature flag on.

func CapturedToken

func CapturedToken(capture *gitlabclient.ResponseCapture) (TokenExtra, error)

CapturedToken reads, off the captured answer to a request for one token, the fields client-go's PersonalAccessToken does not model.

func CapturedTokens

func CapturedTokens(capture *gitlabclient.ResponseCapture, decoded int) ([]TokenExtra, error)

CapturedTokens reads the same off a list answer, one extra per token in order, the count held to what the SDK decoded.

type TokenGranularScopeOutput

type TokenGranularScopeOutput struct {
	Access      string   `json:"access"`
	Permissions []string `json:"permissions"`
	ProjectID   int64    `json:"project_id,omitempty"`
	GroupID     int64    `json:"group_id,omitempty"`
}

TokenGranularScopeOutput mirrors lib/api/entities/personal_access_token_granular_scope.rb, one entry of the granular_scopes array a granular token carries. project_id is set only when the scope's namespace is a project and group_id only when it is a group, so each is absent rather than zero on the other kind.

type ToolError

type ToolError struct {
	Tool       string `json:"tool"`
	Message    string `json:"message"`
	StatusCode int    `json:"status_code,omitempty"`
}

ToolError represents a structured error from a tool handler.

func (*ToolError) Error

func (e *ToolError) Error() string

Error returns a human-readable representation of the tool error. When StatusCode is set, it is appended as "(HTTP <code>)".

type TopicExtra

type TopicExtra struct {
	OrganizationID int64 `json:"organization_id"`
}

TopicExtra is the organization a topic belongs to, which GitLab's topic entity exposes under no condition and so sends on every topic.

func CapturedTopic

func CapturedTopic(capture *gitlabclient.ResponseCapture) (TopicExtra, error)

CapturedTopic reads it off the captured answer to a request for one topic.

func CapturedTopics

func CapturedTopics(capture *gitlabclient.ResponseCapture, decoded int) ([]TopicExtra, error)

CapturedTopics reads the same off a list answer, one extra per topic in order, the count held to what the SDK decoded.

type UploadConfig

type UploadConfig struct {
	MaxFileSize int64
}

UploadConfig holds runtime-configurable upload parameters. Initialized with package defaults; use SetUploadConfig to override from environment config.

func GetUploadConfig

func GetUploadConfig() UploadConfig

GetUploadConfig returns the current upload configuration (for testing).

type UserBasicOutput

type UserBasicOutput struct {
	ID          int64  `json:"id"`
	Username    string `json:"username"`
	PublicEmail string `json:"public_email,omitempty"`
	Name        string `json:"name"`
	State       string `json:"state,omitempty"`
	Locked      bool   `json:"locked"`
	AvatarURL   string `json:"avatar_url,omitempty"`
	WebURL      string `json:"web_url,omitempty"`
}

UserBasicOutput mirrors lib/api/entities/user_basic.rb, the user object GitLab renders on a note's author and resolver and on a runner's creator.

type UserExtra

type UserExtra struct {
	CommitEmail       string `json:"commit_email"`
	Discord           string `json:"discord"`
	GitHub            string `json:"github"`
	LocalTime         string `json:"local_time"`
	PreferredLanguage string `json:"preferred_language"`
	Pronouns          string `json:"pronouns"`
	WorkInformation   string `json:"work_information"`
	Followers         *int64 `json:"followers"`
	Following         *int64 `json:"following"`
	IsFollowed        *bool  `json:"is_followed"`
}

UserExtra is what lib/api/entities/user_public.rb, and the User it inherits, send on a user that client-go's User does not carry. It is the set every route presenting UserPublic answers with, which is what the group-scoped user lists (enterprise users, provisioned users, SAML users) and the instance-wide ones alike present, so the four packages publishing a user share it.

Seven of the ten are exposed under no condition. The three counts are gated on Ability.allowed?(current_user, :read_user_profile, user) together with following_users_allowed, so the same endpoint sends them to a caller who may read the profile and to nobody else. Each is a pointer because zero followers and a profile this caller may not read are different answers, and a bare number would tell them apart from nothing.

func CapturedUser

func CapturedUser(capture *gitlabclient.ResponseCapture) (UserExtra, error)

CapturedUser reads, off the captured answer to a request for one user, the fields client-go's User does not model.

func CapturedUsers

func CapturedUsers(capture *gitlabclient.ResponseCapture, decoded int) ([]UserExtra, error)

CapturedUsers reads the same off a list answer, one extra per user in order, the count held to what the SDK decoded.

type UserIdentity

type UserIdentity struct {
	UserID   string
	Username string
	// Instance is the GitLab instance the identity was resolved against, when
	// the caller knows it.
	//
	// A GitLab user id is unique within an instance and means nothing across
	// them, so a deployment publishing several through --gitlab-url can log two
	// different people as user_id 7. ADR-0008 fixes this struct as
	// {UserID, Username} and motivates it expressly for audit logging, without
	// recording that limit; an audit trail whose subject is ambiguous is the one
	// place it matters.
	//
	// Empty where there is nothing to say: stdio resolves one identity at
	// startup against one instance, and a deployment that pins --gitlab-url has
	// only the one. The field is filled where it is already known rather than
	// resolved again: the HTTP gate computes the instance per request to pick a
	// pool entry and used to throw it away.
	Instance string
}

UserIdentity holds the authenticated user's identity. Populated from OAuth TokenInfo (HTTP modes) or from the startup-resolved identity stored in context (stdio mode).

func IdentityFromContext

func IdentityFromContext(ctx context.Context) UserIdentity

IdentityFromContext retrieves the UserIdentity stored in the context. Returns a zero-value UserIdentity if none was stored.

func ResolveIdentity

func ResolveIdentity(ctx context.Context, req *mcp.CallToolRequest) UserIdentity

ResolveIdentity returns the authenticated user's identity by checking two sources in priority order:

  1. req.Extra.TokenInfo (populated by SDK in HTTP modes via RequireBearerToken)
  2. Context-stored identity (populated at startup in stdio mode)

Returns a zero-value UserIdentity if neither source has identity.

func (UserIdentity) IsAuthenticated

func (u UserIdentity) IsAuthenticated() bool

IsAuthenticated returns true if the identity contains a non-empty UserID.

type UserRefOutput

type UserRefOutput struct {
	ID        int64  `json:"id"`
	Username  string `json:"username"`
	Name      string `json:"name"`
	State     string `json:"state"`
	AvatarURL string `json:"avatar_url,omitempty"`
	WebURL    string `json:"web_url,omitempty"`
	CreatedAt string `json:"created_at,omitempty"`
}

UserRefOutput mirrors gl.BasicUser as surfaced on user resources (the created_by object): identity fields always present, avatar/web URL and created_at omitted when empty. It differs from BasicUserOutput (pipeline sub-objects), whose avatar_url and web_url are always serialized.

func NewUserRefOutput

func NewUserRefOutput(u *gl.BasicUser) *UserRefOutput

NewUserRefOutput converts a *gl.BasicUser into the user-resource reference shape, returning nil when the SDK value is nil.

type VoidOutput

type VoidOutput struct {
	HintableOutput
	Status  string `json:"status"`
	Message string `json:"message"`
}

VoidOutput is a confirmation message returned by tool handlers that perform an action without returning domain data (e.g., set header, start mirroring).

func VoidResult

func VoidResult(message string) (*mcp.CallToolResult, VoidOutput, error)

VoidResult builds a VoidOutput and its Markdown representation for a successful void operation. The message describes what happened.

type WikiExtra

type WikiExtra struct {
	WikiPageMetaID int64          `json:"wiki_page_meta_id"`
	FrontMatter    map[string]any `json:"front_matter"`
}

WikiExtra is what GitLab's wiki page entity sends beside the title and the content: the identifier of the page's metadata record, and the YAML front matter parsed out of the page, which is a map of whatever keys the author wrote. Both are exposed under no condition, and the front matter is empty on a page that has none.

func CapturedWiki

func CapturedWiki(capture *gitlabclient.ResponseCapture) (WikiExtra, error)

CapturedWiki reads them off the captured answer to a request for one page.

func CapturedWikis

func CapturedWikis(capture *gitlabclient.ResponseCapture, decoded int) ([]WikiExtra, error)

CapturedWikis reads the same off a list answer, one extra per page in order, the count held to what the SDK decoded.

Jump to

Keyboard shortcuts

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