Help us shape the future of Go! Take the 2026 Go Developer Survey

toolutil

package
v3.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 30, 2026 License: MIT Imports: 45 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. A result about one GitLab object is written by Card; a collection is a table, and the shared helpers MarkdownTableHeader and MarkdownTableRow keep its 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 (
	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 superseded by the Card rows of the same name: Card.Int for the ID, Card.Field for the status, Card.Time for the two timestamps, and Card.Warn for a failure flag, which marks the condition with the warning sign rather than rendering the boolean as "true".

View Source
const (
	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 row patterns.

The card rows and the heading formats that used to sit here are gone: Card writes both, through one writer that escapes the value at the write and keeps the heading level of the block it is nested in. A format string cannot do either, which is why a caller holding one wrote the escaping itself or forgot to.

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

Contextual emoji constants for consistent visual indicators across formatters.

EmojiWarning is the negative-polarity flag Card.Warn writes: a condition such as revoked, locked, expired or has failures is marked with it and never with the tick BoolEmoji gives a true, which on such a label reads as success.

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 DateFormatISO = "2006-01-02"

DateFormatISO is the date-only layout GitLab uses for a due date or a start date. The literal-Z timestamp layout that sat beside it is gone: it stamped whatever wall clock a value carried with a zone it may not have been in, and RFC3339 and RFC3339Ptr convert to UTC before they write.

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 GraphQLQuickActionsStatusSelection = "quickActionsStatus { commandNames commandsOnly errorMessages messages }"

GraphQLQuickActionsStatusSelection is the selection every createNote and updateNote document sends beside its note, spelled once so that a document cannot ask for part of it: GitLab's account of the quick actions the body carried, which is the only place a caller learns whether "/label ~bug" was applied, refused, or was the whole of a body GitLab therefore kept no note for. The documents concatenate it as a constant, so the schema and shape gates judge the string GitLab receives.

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 RedactedPlaceholder = "[redacted]"

RedactedPlaceholder is what a formatter writes instead of a value it will not echo, so that a reader sees something was withheld rather than that a tool had nothing to report. It is the one spelling every such site uses, so a reader who has met it once recognizes it everywhere.

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 "| Field | Value |" detail table header and separator.

It is the table form of a one-object card, which Card supersedes: a card row is a list item, and a table is for a collection of objects that share columns. The constant stays until every formatter that opens a card as a table has moved; a new formatter starts a card with NewCard.

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.

View Source
const UserCoreRefSelection = `
        username
        name
        webUrl
      `

UserCoreRefSelection selects a person GitLab names on a vulnerability or a finding: enough to say who it was and to link to them. The person's own fields are the users domain's to publish.

View Source
const VulnerabilityLinkSelection = `
        name
        url
      `

VulnerabilityLinkSelection selects a reference the security report attached to a vulnerability or a finding: an advisory, a write-up, a fix.

View Source
const VulnerabilityLocationSelection = `
      ... on VulnerabilityLocationSast {
        file
        startLine
        endLine
        blobPath
        vulnerableClass
        vulnerableMethod
      }
      ... on VulnerabilityLocationSecretDetection {
        file
        startLine
        endLine
        blobPath
        vulnerableClass
        vulnerableMethod
      }
      ... on VulnerabilityLocationCoverageFuzzing {
        file
        startLine
        endLine
        blobPath
        vulnerableClass
        vulnerableMethod
        crashAddress
        crashType
        stacktraceSnippet
      }
      ... on VulnerabilityLocationDast {
        hostname
        path
        requestMethod
        param
      }
      ... on VulnerabilityLocationDependencyScanning {
        file
        blobPath
        dependency {` + vulnerableDependencySelection + `}
      }
      ... on VulnerabilityLocationContainerScanning {
        image
        operatingSystem
        containerRepositoryUrl
        dependency {` + vulnerableDependencySelection + `}
      }
      ... on VulnerabilityLocationClusterImageScanning {
        image
        operatingSystem
        dependency {` + vulnerableDependencySelection + `}
        kubernetesResource {
          kind
          name
          namespace
          containerName
          clusterId
          agent {
            name
          }
        }
      }
      ... on VulnerabilityLocationGeneric {
        description
      }
`

VulnerabilityLocationSelection selects a location through every member of GitLab's VulnerabilityLocation union. A union carries no fields of its own, and a member the document does not name comes back as an empty object, so a member left out here is a scan type whose findings carry no location at all.

View Source
const VulnerabilityTokenStatusSelection = `
        status
        lastVerifiedAt
        createdAt
        updatedAt
      `

VulnerabilityTokenStatusSelection selects what GitLab last learned about the secret a secret detection finding exposed. The record's global id is left out: no action of this server takes it.

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 (
	// ContentUser marks a block for the user's display rather than the
	// model's reasoning: the image block of an upload or a visualization,
	// which a text-only model cannot read and a person is shown.
	// [ToolResultWithImage] and [FinishToolResult] put it on every image
	// block; the text beside it stays with the assistant.
	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 the content blocks of a tool result. 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 a GitLab access level to its human-readable label. A level the table does not name is rendered with its number, "Level 35", because the number is what GitLab sent and the one thing a reader can act on; "Unknown" told them nothing, which is why two packages kept a copy of this table with the numeric fallback of their own.

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 AnnotationsForContentKind added in v3.1.0

func AnnotationsForContentKind(kind string) *mcp.Annotations

AnnotationsForContentKind maps an action's declared content kind (ActionSpecContentList, ActionSpecContentDetail, ActionSpecContentMutate, ActionSpecContentAssistant, ActionSpecContentImage) to the content annotation its text block carries, in one place, so the kind a spec declares and the annotation the dispatcher serves cannot disagree. An image action's text block is for the assistant like any other; the image block itself carries ContentUser. An empty or unknown kind is the assistant default.

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. A listing is charged first to a fourth bucket, the one the whole process shares and counts in tools listed ([processCatalog]), because the entry's multiplies by however many credentials a caller holds; that one refuses with the same code and the same words, and only the log line it writes says which refused.

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. Which bucket each method draws on, and so which draws on none, is tenancy.MeterFor's answer (register rows RTC-001 to RTC-004); the buckets and the refusals stay here. 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. A listing is also charged to the bucket the whole process shares ([processCatalog]), wherever the entry's own listing bucket charges it.

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.

The operation was declined, so it produced none of the output its schema describes. A tool that declares an outputSchema MUST return structured results conforming to it, and a cancellation conforms to nothing, so this is reported as an error result rather than as a success carrying only prose, in the envelope every refusal travels in.

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. It describes the status alone, so a 401 gets the sentence true of both of its causes; ClassifyError, which holds the whole response, can say which one.

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 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.

FinishToolResult calls it last, after the hints have been set on the output, so the JSON the block carries and the structured output of the same result are one serialization: an embed run before the hints carried a body that lacked the next_steps the structured output had.

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.

The block is annotated ResourceMachineDetail: it is the machine-facing payload of one entity, for the assistant deciding what to call next and for a programmatic client, with no display value for a person, which is the one resource family that drops the user role.

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.

A nil result and an empty URI are decided by EmbedResource, which is the one place a block is appended and already refuses both; repeating the two checks here only meant that no test could tell either copy from the other. The toggle is checked here as well, and that one earns its place: it is what keeps a deployment with embedded resources off from marshaling every entity it is never going to embed.

func EmbeddedResourcesEnabled

func EmbeddedResourcesEnabled() bool

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

func EmptyMessage added in v3.1.0

func EmptyMessage(resource string) string

EmptyMessage is the one sentence an empty list renders, "No <resource> found." with its newline, the whole response of a list with nothing in it. The resource is a plural noun the formatter names ("merge requests", "protected branches"). It replaces the heading rather than sitting under it: a heading counting zero above a sentence saying so said it twice.

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, through ErrorResultAnnotated with the refusal annotation. The message is the server's own sentence, written as prose.

func ErrorResultAnnotated added in v3.1.0

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

ErrorResultAnnotated builds the one error envelope every refusal this server writes travels in: IsError set, the Markdown as the one text block through NormalizeResultMarkdown, and the annotation given, or ContentMutate for nil, since a refusal is something the model has to act on. The five hand-built literals it replaced carried no annotation and skipped the normalization every success got.

func ErrorResultMarkdown

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

ErrorResultMarkdown creates an MCP tool error result carrying the DetailedError card for err, through ErrorResultAnnotated: it is the one path a handler has to a rich error result, and it travels in the same envelope as every other refusal.

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 and the opening square bracket are 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 or a live link in a heading after being neutralized in a cell. A heading therefore carries no link: a card's address is its URL row.

func EscapeMdLinkDestination

func EscapeMdLinkDestination(url string) string

EscapeMdLinkDestination renders url as the destination of a Markdown link. It contains the delimiters and nothing else: whether the value may be a destination at all is LinkableDestination's question, asked by the callers that decide to write a 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.

The opening square bracket is an entity as well, &#91;, because a value may be a link only when this server wrote the link around it. A title of

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

passed through here as it was, so a cell or a list value the server meant as text rendered as a working link to a host that is not GitLab, and HintPreserveLinks then told the model to keep it clickable. The one consequence worth knowing is that a finished link does not survive this function: a link is built by MdTitleLink from its raw halves and is never escaped afterwards, and a cell that holds one is written as it is.

Only '<' and '[' are escaped, since everything a renderer obeys instead of reading opens with one of them (a tag, a comment, an autolink, a link, an image). '>' and ']' are 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.

The result is idempotent: nothing this function writes is anything it rewrites, so a value escaped twice renders once, which is what lets a caller that still escapes by hand pass its value to a helper that escapes again.

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, a refusal GitLab answers at the top level is reported as that refusal, and the first mutation payload error becomes "op: message".

The document selects the errors alone. The payload type offers a note and a quick actions status as well, and Mutations::Notes::Destroy answers with neither: its resolver returns the errors and nothing else, so both are null on every response.

The top-level check is what keeps a refused delete from reading as a successful one. A note that is gone or that the token may not delete is refused with HTTP 200, a top-level error and a null payload, which client-go does not turn into an error, and a null payload carries no payload error either.

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 value of the parameter of that name escaped as RFC 6570 simple expansion escapes it ([escapeSimpleExpansion]), so a project given as "group/project" lands in the URI as group%2Fproject and a scoped label priority::high as priority%3A%3Ahigh, which is the form the resource templates accept and the resource handlers decode once; {+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 the GitLab response err carries, found over REST and GraphQL alike by [gitLabResponseOf]. 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.

The unparsed-body drop belongs to [boundedGitLabMessage], which every path out of here returns through, so it is not tested a second time on the way in: a copy of that test could only agree with the one below it.

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, or after the body, so it closes it. The leading form is a defect the migration to one card vocabulary retires, not an idiom, and the number of formatters still using it only shrinks; it is read here because a response that has it carries no other section. 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 FinishToolResult added in v3.1.0

func FinishToolResult(callResult *mcp.CallToolResult, result any, route ActionRoute, params map[string]any) (finished *mcp.CallToolResult, structured any)

FinishToolResult is the one tail every dispatcher applies to a formatted result, so an envelope rule cannot be added to two dispatchers of three again. In order: a nil result, which a formatter returning "" produces, becomes the JSON rendering rather than reaching the client with no content block; an error result is returned as it is, with no structured output, since the action produced none of what its schema describes; every text block is annotated with the action's declared content kind and every image block for the user; the next-step hints the Markdown carries are set on the typed output when its type declares them; and the canonical resource is embedded last, so the JSON it carries agrees with the structured output, hints included.

The hints are set through the output's own HintSetter, never spliced into its JSON: the individual surface validates the structured output against the schema the type declares, and a next_steps key on a type that declares none would fail that validation, so a type without HintableOutput carries its hints in the Markdown alone.

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 FormatDiscussionMarkdown

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

FormatDiscussionMarkdown renders a single discussion thread as the card of one thread: the heading the card writer escapes, the thread's notes quoted under their authors, and the hints last. The notes are a collection the reader reads rather than a table of columns, so they keep the shape [writeDiscussionNotes] gives them.

The thread id reaches this renderer as a field of the result rather than as a literal the server wrote, and a heading is the one line where a value that is not a digest would open a tag, so the card's heading escaper is what contains it.

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 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 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 card: the identity rows, the description as the card's long text, the priority when GitLab sent one, the two flags as glyphs, and the counters when the caller asked GitLab for them.

func FormatNoteListMarkdown

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

FormatNoteListMarkdown renders a list of GitLab notes as a Markdown table. The table carries no link, so the footer carries no instruction to keep them.

func FormatNoteMarkdown

func FormatNoteMarkdown(note NoteMarkdown, opts NoteMarkdownOptions) string

FormatNoteMarkdown renders a single GitLab note as a card: the author as a handle, the time, the flags that hold, the resolution state when the domain has one, the body as the card's long text, quoted under its label when it spans more than one line, and what the quick actions in the body did when a mutation reported it.

func FormatQuickActionsOnlyMarkdown added in v3.1.0

func FormatQuickActionsOnlyMarkdown(title string, status QuickActionsStatusOutput, hints ...string) string

FormatQuickActionsOnlyMarkdown renders the answer to a note mutation whose body held nothing but quick actions: GitLab ran them and kept no note, so there is no note to show, only what the commands did. title names the kind of note the caller asked for ("Epic Note").

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 FormatSafeModePreviewMarkdown added in v3.1.0

func FormatSafeModePreviewMarkdown(p SafeModePreview) string

FormatSafeModePreviewMarkdown renders a preview as a card, the way every other refusal is rendered: the intercepted tool in the heading, the status and mode rows, the tool as a code span, the would-be arguments in a JSON fence, and the operator's hint last. It is the registered formatter for SafeModePreview, so the dispatcher surfaces render the preview through it, and SafeModePreviewResult is what the individual surface answers with.

The hint is escaped, unlike every other hint this server writes. WriteHints writes a hint as it is given because a hint is the server's own sentence composed at the call site, and one of them, HintPreserveLinks, is a link; here the hint is a field of a struct the formatter is handed, so it is data on the same terms as the status and the mode. Unescaped it could end its own bullet and open a heading, a list item or a second guidance section below the card, which took the real hint out of next_steps with it.

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 card: the identity and state rows, the two storages, the creation time in the display form, and the moved entity as a link with its ID.

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.

The title reaches MdTitleLink raw. It used to be escaped here and again inside the link helper, which was harmless only while the escaper left every character it wrote alone; the rule now is that a link is built from its raw halves once, and escaping a value that is about to be linked is the shape that turns the link back into text.

func FormatTemplateAttributeListMarkdown

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

FormatTemplateAttributeListMarkdown renders a template list as a table, in the two-column and the three-column shape alike, so the two families share one heading, one empty form and one footer. Templates carry no link, so the footer carries no instruction to keep them.

func FormatTemplateContentMarkdown

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

FormatTemplateContentMarkdown renders a GitLab template body as a card whose only content is the body inside a fenced code block, through Card.Fence, which sizes the fence past the longest backtick run in the body. On an instance with custom file templates the set is served out of a designated project, so the name is a repository file name somebody chose, and the heading is escaped as every card heading is.

func FormatTemplateDetailMarkdown

func FormatTemplateDetailMarkdown(detail TemplateDetailMarkdown) string

FormatTemplateDetailMarkdown renders a template detail page as a card: the identity rows, the description as the card's long text, the three lists joined on their rows, and the content in a fence under its heading when the caller named one. The project-template endpoints also serve issue and merge-request description templates, whose name is a file in .gitlab/issue_templates named by whoever pushed it, so the heading is escaped as every card heading is.

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.

It is superseded by RFC3339Ptr, which it is an alias of: the name says display and the output is the wire form, which is how a table came to show RFC 3339 where every other one shows FormatTime's layout. New code calls RFC3339Ptr, or FormatTimeValue for a value a reader sees, and the staticcheck deprecation marker goes on here once the forty-odd callers have moved, since the marker fails the lint gate on every one of them until then.

func FormatTimeValue added in v3.1.0

func FormatTimeValue(t time.Time) string

FormatTimeValue renders t in the display form, or "" for a zero time, which is what a field GitLab did not send decodes to and would otherwise read as the year one.

func GraphQLMutationError

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

GraphQLMutationError formats mutation payload errors, if any.

func GraphQLNumber added in v3.1.0

func GraphQLNumber(s string) int64

GraphQLNumber reads a number GitLab's GraphQL API spells as a String: the iid of an issue or a merge request. A value that is not a number, which the schema allows, reads as zero rather than an error, since the object it sits on is worth reporting whether or not that one value parsed.

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, matching spec.Route.OutputType) or a value of any kind (used by tests). Pointer types are dereferenced to their element type for the registry lookup, exactly as MarkdownForResult dereferences a pointer value. 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 HintAction added in v3.1.0

func HintAction(actionID, purpose string) string

HintAction composes a next-step hint that names an action by its canonical catalog ID, "Use action 'issue.update' to change this issue". The ID is the one form every surface accepts: the dynamic surface executes it directly, and the meta and individual surfaces resolve it to their own tool names, so a hint written this way is never a name the serving surface does not register.

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 GitLab answered err with the given HTTP status code, over REST or over GraphQL. Useful for handling specific API responses like 404 (feature not available on CE) or 403 (insufficient permissions).

The status is read the way ClassifyError reads it, so the two never disagree about one error: a GraphQL refusal is looked through, and a 404 is recognized by client-go's sentinel, which carries no response.

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 IsPermissionRefusal added in v3.1.0

func IsPermissionRefusal(err error) bool

IsPermissionRefusal reports whether GitLab answered err with a refusal that can be a valid credential lacking a permission: a REST 401 or 403 whose body carries no RFC 6750 error code and does not refuse the account itself, which gitlabclient.RefusalMayBePermission decides. It is what a handler keys a hint naming a role, a license or an owner on, where IsHTTPStatus with 403 used to be.

The 401 is the reason it exists. At the routes entry 55 of docs/development/upstream-bugs.md lists, GitLab refuses a missing permission with 401, so a hint scoped to 403 was never shown there. A hint scoped to 401 alone is wrong the other way: ClassifyError describes a 401 that GitLab said was about the credential as the token itself being refused, and a role suggestion after that verdict contradicts it. This predicate is false for those answers, for every other 401 or 403 the API guard writes a code on (a DPoP refusal or a restricted language server client, which ClassifyError does not describe as the token being refused, are among them), for its 403 about a missing token scope, and for its 403 refusing an account the API will not serve (blocked, deactivated, the Terms of Service not accepted and the like), so the hint follows only a description that leaves a permission refusal open. gitlabclient.RefusalMayBePermission holds the full rule.

A handler whose route answers a 403 that means something else, a license or an archived project rather than a role, pairs it with IsHTTPStatus to tell the two apart. The 403 is kept here because it is what these routes would answer if GitLab retired entry 55, and then a handler that keys one hint on this predicate alone needs no change. The handlers that pair it with a status do, since each reads the status as the cause: the security settings routes, the three external status check merge request routes, the fork link and the merge train add. Those are the ones to revisit when entry 55 is retired.

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 LinkableDestination added in v3.1.0

func LinkableDestination(url string) bool

LinkableDestination reports whether url may be written as the destination of a link or an image: an absolute http or https address, and nothing else.

The escaper above contains the delimiters, so a value cannot end the link it is in, and it says nothing about the scheme. A javascript, data or vbscript destination is a whole link that a client renders live, and it is the client's renderer that decides what a click does: goldmark drops such a destination on its own, and a chat surface that hands the href through does not. Every address this server links is one GitLab built from its own origin, so the allow list costs nothing legitimate. The check runs on the value with its control bytes gone, because "java\x00script:" is the escaped destination "javascript:" and would pass a check made on the bytes as sent.

func ListHints

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

ListHints prepends HintPreserveLinks to list-result next-step hints, once: an empty hint or a copy of HintPreserveLinks among the arguments is dropped, so a caller that already names it does not get it twice. The hints it returns are for the section a list formatter writes after its table. Writing that section first, on an empty builder, is a defect rather than an idiom: the guidance then opens the response, ahead of the data it is about, and any hint the formatter appends afterwards can never reach next_steps, since ExtractHints reads a section only at one end of the response.

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. A nil input renders the success confirmation; a value of an unregistered type, or a typed nil pointer, renders nil for the dispatcher to fall back on. A pointer to a registered type is dereferenced first, which is the same question HasRegisteredMarkdownFormatter answers: the two used to disagree, so a handler returning a pointer had a formatter the coverage test could see and the runtime never called.

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 MarkdownRegistrationProblems added in v3.1.0

func MarkdownRegistrationProblems() []string

MarkdownRegistrationProblems returns every registration the registry refused or could only half honor, sorted, for a test to assert there are none.

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 MdCodeSpan added in v3.1.0

func MdCodeSpan(s string) string

MdCodeSpan renders a GitLab-authored value as an inline code span, for a value the reader has to copy exactly: a token, a variable key, a path, a SHA, a command.

A code span is its own containment, so nothing inside it is Markdown and no entity is written: "&#124;" and "&lt;" render literally inside one, which is what the sites that filled a span with EscapeMdTableCell were showing. What a value can still do is end the span with a backtick run of its own, so the fence is one backtick longer than the longest run inside, the rule Markdown itself uses for nesting code. Line breaks collapse to a space, since a span ends with its line, and control bytes are dropped. A value that begins or ends with a backtick is padded with a space on both sides, which CommonMark strips again. An empty value renders as nothing rather than as two bare backticks, which read as a span that never closes.

func MdCodeSpanCell added in v3.1.0

func MdCodeSpanCell(s string) string

MdCodeSpanCell renders a value as MdCodeSpan does, for a table cell: the pipe is backslash-escaped, which GFM honors inside a code span in a table and which is the one escape that works there, since the entity a cell would otherwise use renders literally inside the span.

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, and a destination that is not an http or https address is not linked at all: the address is written in a code span, after the title when the title says something else, so the reader keeps what GitLab sent and nothing in it is live. A websocket endpoint or a wiki attachment's relative path lands here as well as a hostile value, and a code span is the honest rendering of an address a client could not have opened anyway.

func MdUserHandle added in v3.1.0

func MdUserHandle(username string) string

MdUserHandle renders a username as the "@handle" GitLab shows, escaped for a card row or a cell, or nothing for an empty name, so a card never shows a bare "@".

func MdUserLink(username, webURL string) string

MdUserLink renders a username as its handle linked to the user's profile, through MdTitleLink, the escaped handle alone when the profile URL is empty, and nothing for an empty name.

func MergeRequestDiffsForPositionCheck added in v3.1.0

func MergeRequestDiffsForPositionCheck(ctx context.Context, client *gitlabclient.Client, projectID string, mrIID int64) ([]*gl.MergeRequestDiff, bool)

MergeRequestDiffsForPositionCheck lists a merge request's diffs for a position pre-check and reports whether it could.

The pre-check exists to hand a model an actionable message before GitLab answers a line outside the diff with a bare 400 or 500, and it is advisory: when the listing itself fails the second result is false and the caller skips the check rather than refusing the write. What is skipped is the message, never the judgement, because the write that follows goes to the same instance with the same credential and GitLab answers it on its own terms. A 401, 403 or 404 on the listing is the refusal the write is about to get, with the right status; a cancelled context ends the write the same way; and a transient failure on the listing leaves the write judged by GitLab alone, which is exactly what a caller had before the pre-check existed. The cause is logged at debug so that a listing which fails every time can be seen without the write having to fail.

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 action a historical spelling stands for on the meta-tool the call was sent to, and the spelling itself when the tool claims none.

The aliases are the ones the group's own routes carry (ActionRoute.CompatibilityAliases), projected by the catalog from each action's compatibility policy, which is `internal/tools/actioncompat` and so the one table the dynamic surface resolves against too. There used to be a second table in this file, keyed by legacy spelling and valued by canonical ID; a route map is keyed by bare action names, so every dotted value in it could never be found and about ninety of its ninety-three entries were unreachable. `milestone.get` was in that table and resolved on no surface.

An action the tool routes is returned as it is. An alias is another spelling of one action, and a name the tool already routes is an action of its own: rewriting it runs a different handler than the one the caller named. That is what group_board_list did on a licensed instance, where epic_board_list also exists on gitlab_group and the alias turned a request for the group's issue boards into its epic boards.

An alias two of the group's own actions claim is left alone rather than resolved to whichever the map iteration reached first. The catalog refuses such a group, so this only guards a route map assembled by hand.

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. The dynamic surface normalises with it before dispatch, and FinishToolResult, through embedParams, expands the embedded resource URI from its output, so both see the parameters as the handler's UnmarshalParams saw them.

func NormalizeResultMarkdown added in v3.1.0

func NormalizeResultMarkdown(md string) string

NormalizeResultMarkdown is the one normalization every rendered response passes through on its way into a content block: trailing spaces and tabs dropped from each line, and control bytes dropped everywhere, since a formatter that writes a GitLab field straight into its builder would otherwise deliver an escape sequence to whatever prints the text. It is in one place so the constructor a formatter happens to be registered through cannot change the rendering. See StripControlBytes.

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 card 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. It is annotated for a detail, since it answers a get.

Both halves of the sentence are contained here, each by the escaper its slot takes: the identifier because it is text the caller typed and a get handler echoes it as it arrived, and the label because it is not always the constant this once claimed it was. Two packages read it off the result instead, and they disagreed about whose job the escaping was: badges escaped it at its own call site and orbit passed it through raw, which is the shape a rule stated in a comment rather than in code always ends up in. The heading takes the same label through the card writer, which applies the heading escaper.

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 PaginationRequestOption added in v3.1.0

func PaginationRequestOption(page PaginationInput) gl.RequestOptionFunc

PaginationRequestOption carries a caller's page and per_page to a client-go method that takes no options struct to put them in. client-go models several of GitLab's paginated lists that way (a user's GPG keys and emails, system hooks, project aliases, a commit's merge requests), and without this such a method can only ever ask for GitLab's first page.

A value the caller left at zero is not sent, so GitLab applies its own default, exactly as ApplyListOptions leaves a zero out of a gl.ListOptions. The option runs after client-go has encoded the request's own query, so it adds to that query rather than replacing it.

func ParamText added in v3.1.0

func ParamText(value any) string

ParamText renders a tool argument's value the way the caller wrote it, for the identifier a not-found result names. A route wrapper reads the arguments as they were decoded, before anything coerced them, so a JSON number arrives as a float64, and %v prints one of a million or more in exponent form: a package_id of 31234567 was named "3.1234567e+07" on the dynamic, meta and individual surfaces alike. A whole number is printed as the integer it is, and anything else as fmt prints it.

It renders a value and never looks up a name: which key the value is read from is ActionRoute.WrapNotFound's concern, which hands its builder the arguments with the documented aliases already resolved.

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 RFC3339 added in v3.1.0

func RFC3339(t time.Time) string

RFC3339 renders t in the wire form, RFC 3339 in UTC, or "" for a zero time.

func RFC3339Ptr added in v3.1.0

func RFC3339Ptr(t *time.Time) string

RFC3339Ptr renders an optional time in the wire form, or "" when nil.

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 RedactURL added in v3.1.0

func RedactURL(u *url.URL) string

RedactURL renders u with the three parts a secret can hide in removed: the userinfo, the query and the fragment. What survives is the scheme, the host and the path, which is what identifies the destination.

A URL reaches a model through this server whenever a tool reports where it is talking to, and each of the three dropped parts is somewhere a credential is routinely written: a proxy login in the userinfo, a signed token in the query, a fragment carried over from a browser. None of them identifies the destination, so none of them is worth the risk of echoing.

The path survives because for an instance URL it names the installation (a GitLab served under a subpath) rather than the caller, and because client-go builds its own error text from the same three parts, which leaves the two agreeing instead of this being the one place that says more. Use RedactURLToOrigin for a destination whose path is itself the secret.

func RedactURLToOrigin added in v3.1.0

func RedactURLToOrigin(raw string) string

RedactURLToOrigin parses raw and renders its origin alone: the scheme and the host. It drops what RedactURL drops and the path with it, replacing a path, query or fragment that was there with "/..." so a reader can see that something was withheld.

The path goes because for a webhook, an integration endpoint or any other callback URL the path is where the secret lives: a Slack or Teams hook is a public host with an unguessable path, and truncating such a URL to a fixed number of characters leaks the front of it, userinfo included. The host is what an auditor reads these values for, and the host is all that is left.

Text that does not parse as a URL, or that names no scheme and host, is reported as RedactedPlaceholder rather than echoed: a value that is not a URL is a value nothing here has judged.

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, or a pointer to one, will invoke fn and wrap the returned string in a mcp.CallToolResult annotated for the assistant.

func RegisterMarkdownAnnotated added in v3.1.0

func RegisterMarkdownAnnotated[T any](fn func(T) string, ann *mcp.Annotations)

RegisterMarkdownAnnotated registers a Markdown string formatter for type T whose results carry ann, one of the ContentList, ContentDetail and ContentMutate presets; nil means ContentAssistant. The dispatchers override it with the annotation the action's declared content kind resolves to, so a formatter reached through the catalog is annotated by the spec and a formatter reached any other way by this.

A second registration for a type is refused and recorded: the first one stays, since a registration that silently replaced another is how one output type came to render another action's heading. An interface type is refused and recorded too, because its registration lands under a key no concrete value ever looks up.

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). A second registration for a type, or a registration for an interface type, is refused and recorded on the same terms as RegisterMarkdownAnnotated.

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 RegisteredMarkdownFormatterName added in v3.1.0

func RegisteredMarkdownFormatterName(t reflect.Type) string

RegisteredMarkdownFormatterName returns the name of the formatter registered for t, the served one where two were registered, or "" when none is.

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 RegisteredMarkdownTypes added in v3.1.0

func RegisteredMarkdownTypes() []reflect.Type

RegisteredMarkdownTypes returns every type a Markdown formatter is registered for, string and result variants alike, sorted by name so a scan over them is deterministic. It is the seam the runtime structural gate drives: a fixture can be built for a type where only a name could not.

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 SafeModePreviewResult added in v3.1.0

func SafeModePreviewResult(p SafeModePreview) *mcp.CallToolResult

SafeModePreviewResult renders a preview as the error result the individual surface answers an intercepted call with: the tool did not run, so it produced none of the output its schema describes, and the specification is unconditional about what a declared schema obliges.

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 SeverityBadge added in v3.1.0

func SeverityBadge(severity string) string

SeverityBadge renders a vulnerability or finding severity as its color glyph and the level in capitals, the one badge the security domains share. A level the table does not know is rendered escaped, since it is a value GitLab sent.

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 success mcp.CallToolResult carrying the Markdown as its one text block, annotated for the assistant. Empty Markdown yields nil, which the dispatchers render as JSON through FinishToolResult; it is how a nil action result and a result with no formatter are answered.

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. The text block carries ann, or the assistant default, and the image block carries ContentUser: the image is for a person's display, the text for the model's reasoning.

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. The text passes through NormalizeResultMarkdown, as every rendered response does.

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, after the classification and GitLab's message and before the cause:

"branchDelete: conflict: the resource already exists or there is a state conflict ({message: Cannot delete: protected branch}). Suggestion: use branch.unprotect first, then retry deletion: DELETE https://gitlab.example.com/api/v4/projects/1/repository/branches/main: 409 {message: Cannot delete: protected branch}"

The hint should be a concise suggestion starting with a verb (e.g., "use branch.list to verify the branch name"), and names an action by its canonical ID rather than by a tool name, which only one surface of three registers; cmd/audit_action_ids fails on one.

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. The composition is the operation, the classification, GitLab's message in parentheses, and the sanitized cause, which carries the request line, the status and that same message once more:

"fileCreate: bad request: check your input parameters ({message: A file with this name already exists}): POST https://gitlab.example.com/api/v4/projects/1/repository/files/README.md: 400 {message: A file with this name already exists}"

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 WriteDownloadOutputFile added in v3.1.0

func WriteDownloadOutputFile(path string, write func(io.Writer) error) (_ int64, err error)

WriteDownloadOutputFile writes a download to the destination a caller named, and only once write has produced all of it. It returns the number of bytes written.

The destination is resolved and confined by [canonicalDownloadOutputPath] before anything is created, so one outside the allowed roots leaves no directory behind either. It is resolved again once its directory exists: the first pass could only vouch for the ancestors that were there at the time, and a symlink planted under a directory this call just made would otherwise decide where the bytes land.

write receives a temporary file in the destination's directory, never the destination. When write returns nil the file is synced, closed and renamed over the destination, and on Unix the directory is synced after the rename; when write returns an error, or the file cannot be put in place, the temporary file is removed and the error returned, so the destination holds exactly what it held before, or is still absent. That is the point of the indirection: a download cut off by an error answer, a dropped connection or a cancelled call used to leave an empty or truncated file at the destination, which the next reader took for the download. The directories made on the way stay, since they are empty and a retry needs them. A process killed outright leaves its temporary file, recognizable by [partialDownloadPrefix], and still never a partial destination.

The rename is also what closes the race [canonicalDownloadOutputPath] cannot: it resolves a symlink already at the destination and confines what that link names, but a local principal who can write in an allowed root could plant one after the check. A rename replaces the directory entry at the destination and does not follow it, so a symlink planted there is replaced rather than written through, on every platform. On Unix the replacement is one rename(2) and atomic. On Windows it is MoveFileEx with MOVEFILE_REPLACE_EXISTING, which Go does not promise to be atomic and which fails while another process holds the destination open without sharing its deletion, a running program for one, or when the destination is marked read-only; it then keeps its previous content and the error says so.

A replaced destination is a new file, and a hard link to the old file keeps the old content. On Unix the new file is readable and writable by the owner alone (created with mode 0600), like every file this creates, whatever the file it replaces allowed; on Windows it takes the access the directory grants to new files, not the old file's own ACL, since Go creates it with no security descriptor of its own and the rename carries the one it inherited. On Unix, whether it may be replaced at all is decided by the directory's permissions, as for any rename, rather than by the old file's own.

func WriteGraphQLPagination added in v3.1.0

func WriteGraphQLPagination(b *strings.Builder, p GraphQLPaginationOutput, shown int)

WriteGraphQLPagination writes the cursor summary of a GraphQL list after a blank line, the one way a cursor-paginated list ends: the line used to be written four ways across ten packages, one of them with no blank line in front of it, which glued it to the last row of the table.

func WriteHints

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

WriteHints appends the server's guidance section to the Markdown builder: a horizontal rule, the "💡 Next steps" heading, and one bullet per hint. Each hint is a short string describing a related action the model can take (e.g. "Use action 'delete' to remove this package"). If no hints are provided, no section is written.

The section separates itself from whatever precedes it. The rule is written after exactly one blank line, whatever the builder ended with. A formatter that stopped mid-line used to get "text\n---", which CommonMark reads as a setext heading: its last line became an H2, the rule vanished into it, and nothing downstream could find the section, so its hints never reached next_steps. Trailing newlines are trimmed first so the blank line is one and not three. On an empty builder the section opens the response, after the one newline [leadingHintsBlock] accepts, rather than at the very first byte, where a "---" reads as YAML front matter to a renderer that looks for it.

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, the raw file, job trace, snippet and discussion renderers that embed GitLab bytes verbatim included, so doing it here covers them all without each renderer having to remember, and covers the ones added later.

A hint is written as it is given, control bytes aside: it is the server's own sentence, and a GitLab-authored value inside one has to be escaped by the formatter that composed it. The escaping gate follows this write back through every caller, and at L1-08 every hint a domain formatter passes classifies safe; the ten arguments that do not are this package's own plumbing of those hints through an options struct, a renderer constructor, a method receiver or a registered formatter's output type, which the classifier does not follow to the literal the domain wrote. The write is therefore declared below rather than judged, and the declaration retires when the classifier follows those shapes: it fails the gate as stale the moment nothing needs it.

A blank hint is dropped, and a call whose every hint is blank writes no section at all. A hint reaches a formatter as a field of its result as often as it does as a literal, and a result built from a zero value carries those fields empty: written out, each became a bare "- " and the first of them, landing directly under the sentence above it, turned that sentence into a setext heading. Guidance with no text in it is not guidance, so the shape that damages the document is the one that gets dropped rather than each caller having to remember to check.

func WriteHookSecretKeys added in v3.1.0

func WriteHookSecretKeys(b *strings.Builder, urlVariableKeys, customHeaderKeys []string)

WriteHookSecretKeys writes the tables of a webhook's URL variables and custom headers, keys only, with every value redacted: both are secrets GitLab itself never sends back, and the key is what a reader needs to know which ones are set. Either list may be empty, in which case its table is not written.

func WriteListFooter added in v3.1.0

func WriteListFooter(b *strings.Builder, p PaginationOutput, linked bool, hints ...string)

WriteListFooter closes a list result: the pagination footer through WritePagination, then the guidance section. When linked is true the table carried a link column and HintPreserveLinks leads the hints, as ListHints arranges; when it is false the hint is dropped even if the caller passed it, since an instruction to keep the links of a table that has none is noise the model has to read past.

func WriteListHeading added in v3.1.0

func WriteListHeading(b *strings.Builder, title string, shown int, p PaginationOutput)

WriteListHeading writes the H2 a list result opens with, "## Title (N)", and the summary line WriteListSummary adds for a multi-page result.

N is what the response can vouch for: the total GitLab sent when it sent one, the count shown with "more available" when the page has a successor but no total (keyset pagination sends none), and the count shown otherwise. A heading that printed the page length under a larger total, or a zero total above a table of rows, was the commonest way a list misled its reader.

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, and it names no total when GitLab sent none rather than saying "of 0".

func WritePagination

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

WritePagination appends the pagination footer after a blank line, whatever the builder ends with, so the footer opens a paragraph of its own rather than continuing the last table row or list item as a lazy line. A pagination that says nothing (no page, no total, no page size) writes nothing, since "Page 0 of 0 | 0 items total" is an answer GitLab never gave.

Types

type AISettingsOutput added in v3.1.0

type AISettingsOutput struct {
	DuoAgentPlatformEnabled             bool   `json:"duo_agent_platform_enabled"`
	DuoWorkflowMCPEnabled               bool   `json:"duo_workflow_mcp_enabled"`
	FoundationalAgentsDefaultEnabled    bool   `json:"foundational_agents_default_enabled"`
	AICatalogRestrictedToGroupHierarchy bool   `json:"ai_catalog_restricted_to_group_hierarchy"`
	AIUsageDataCollectionEnabled        bool   `json:"ai_usage_data_collection_enabled"`
	IncludeRecommendedAllowed           bool   `json:"include_recommended_allowed"`
	AllowAllUnixSockets                 bool   `json:"allow_all_unix_sockets"`
	AllowProjectExtension               bool   `json:"allow_project_extension"`
	PromptInjectionProtectionLevel      string `json:"prompt_injection_protection_level,omitempty"`
	MinimumAccessLevelExecute           string `json:"minimum_access_level_execute,omitempty"`
	MinimumAccessLevelExecuteAsync      string `json:"minimum_access_level_execute_async,omitempty"`
	MinimumAccessLevelManage            string `json:"minimum_access_level_manage,omitempty"`
	MinimumAccessLevelEnableOnProjects  string `json:"minimum_access_level_enable_on_projects,omitempty"`
}

AISettingsOutput is the ai_settings object ee/lib/api/entities/ai_settings.rb renders. It lives here rather than in a domain package because two entities expose it: GroupDetail, and the application setting.

The four minimum-access-level keys are gated on the dap_group_customizable_permissions feature flag, so each is omitted rather than sent empty when the flag is off.

type AccessRequesterExtra added in v3.1.0

type AccessRequesterExtra struct {
	Locked      bool   `json:"locked"`
	PublicEmail string `json:"public_email"`
	AvatarURL   string `json:"avatar_url"`
	WebURL      string `json:"web_url"`
}

AccessRequesterExtra is what lib/api/entities/access_requester.rb sends on a pending access request that client-go's AccessRequest does not carry.

It is deliberately not MemberExtra, and this is the whole point of the shape. The entity does not inherit Member: it exposes `user` merged from UserBasic and `requested_at`, and nothing else, which the live record in docs/development/gitlab-api-live.json states as those two fields. A pending request is not a membership, so it carries no access level, no expiry, no role, no membership state and no SAML or SCIM identity, and the four keys here are the UserBasic ones client-go leaves out. This shape used to embed MemberExtra on the strength of a comment saying the entity inherits Member, which it does not, and ten membership keys were published on a response that has never carried one of them.

The same UserBasic exposes avatar_path and custom_attributes, and neither is read: both wait on a presenter option, and no access-request route declares only_path or with_custom_attributes, so GitLab has never sent either here.

func CapturedAccessRequester added in v3.1.0

func CapturedAccessRequester(capture *gitlabclient.ResponseCapture) (AccessRequesterExtra, error)

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

func CapturedAccessRequesters added in v3.1.0

func CapturedAccessRequesters(capture *gitlabclient.ResponseCapture, decoded int) ([]AccessRequesterExtra, error)

CapturedAccessRequesters 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
	// CompatibilityAliases are the historical spellings that resolve to this
	// action, lowercased, taken from the action's compatibility policy by the
	// catalog. They travel on the route because the dispatcher sees the route
	// and not the spec: [NormalizeActionAlias] answers out of the group's own
	// route map, and a table kept beside that map cannot know which of its
	// entries the group owns. Aliases above is the search list, which is a
	// different question and is never resolved as a call.
	CompatibilityAliases []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
	// ContentKind is the content kind the action's spec declares, which
	// [FinishToolResult] resolves to the annotation every text block of the
	// result carries through [AnnotationsForContentKind]. The catalog sets it
	// from the spec, so the declared kind and the served annotation are one
	// value.
	ContentKind 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(related ...string) ActionRoute

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

The parameter is named related because cmd/audit_action_ids follows a parameter out to its callers only under a name beginning with related, and so judges the IDs where each caller writes them.

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.

func (ActionRoute) WrapNotFound added in v3.1.0

func (route ActionRoute) WrapNotFound(notFound func(params map[string]any) any) ActionRoute

WrapNotFound returns the route with GitLab's 404 answered by the value notFound builds from the call's arguments, in place of the error, now and on every later rebinding. The value is a typed not-found output whose formatter renders NotFoundResult.

notFound reads the arguments as the handler read them: with the documented parameter aliases resolved against the route's input type, which is what UnmarshalParams does before any handler runs. The meta surface hands a route the arguments as the caller spelled them and resolves the aliases only inside that call, on a copy, so a builder reading the map it was handed named a project given as project_path, or a milestone given as iid, as "<nil>", although the request GitLab refused was for exactly that project. The map notFound receives is a copy wherever anything was resolved, so the handler's own arguments are never changed.

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 ApprovedMemberExtra added in v3.1.0

type ApprovedMemberExtra 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"`
}

ApprovedMemberExtra is what lib/api/entities/member.rb sends on the member an approved access request became, beyond what client-go's AccessRequest carries. Approving is the one route of that family GitLab answers with Member rather than AccessRequester, so it is the one that has a membership to describe: everything MemberExtra reads arrives here, and 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 CapturedApprovedMember added in v3.1.0

func CapturedApprovedMember(capture *gitlabclient.ResponseCapture) (ApprovedMemberExtra, error)

CapturedApprovedMember reads them off the captured answer to approving one access 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 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 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 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 CIMinutesUsageOutput added in v3.1.0

type CIMinutesUsageOutput struct {
	TotalMinutesUsed     int64 `json:"total_minutes_used"`
	MonthlyMinutesUsed   int64 `json:"monthly_minutes_used"`
	PurchasedMinutesUsed int64 `json:"purchased_minutes_used"`
}

CIMinutesUsageOutput is the compute-minute usage ee/lib/api/entities/ci/minutes/usage.rb renders for a namespace in the current billing period: the total, and the part of it drawn from the monthly quota and from purchased minutes. ee/lib/ee/api/entities/namespace.rb sends it only for a top-level namespace whose caller holds admin_ci_minutes, which is its owner or an administrator, and GitLab sends it from 19.4 (namespaces.md). All three are whole minutes, as Ci::Minutes::Usage computes them.

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 Card added in v3.1.0

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

Card is the one shape a tool result about a single GitLab object takes, and the only writer of its rows: the H2 heading the formatter composes, then one "- **Label**: value" item per field, then the object's long text, nested objects and nested collections, then the next-step hints last. A Markdown table is for a collection of objects that share columns and never for the fields of one object, so no method here writes a card row as a table row; the one table a Card writes is the nested collection Card.Table opens, under a heading of its own.

The list shape is the one that survives whatever is written around it. A list item is a complete block wherever it lands, so a formatter can extend a card another formatter started, and a card can follow a quote, a fence or a table. A table row is a row only while nothing but rows has been written since its header, and anywhere else it is a line of literal pipes, which is how two cards in this tree came to print "| Status | merged |" as text while their tests passed on the substring.

A Card streams into the caller's builder rather than buffering a document, so a formatter keeps the seams it has today: a fenced body, a rich-content note, a second formatter adding rows. What keeps that composable is the mark. The card records the builder's length after each of its own writes, and when anything else has been written since, the next row starts after a blank line, so it opens a list of its own instead of continuing a foreign block, and a table or a quote written in between ends where it should.

Every value is escaped at the write that renders it, by the helper the value's shape needs, and a caller passes GitLab text as it arrived: the cell escaper for an inline value, MdCodeSpan for a code span, MdTitleLink for a link, WrapGFMBody for prose. The escaping is idempotent, so a caller that still escapes by hand renders unchanged. An empty, nil, blank or zero value writes nothing where the zero is an absence: the card shows what GitLab sent, and never a label with nothing after it.

func NewCard added in v3.1.0

func NewCard(b *strings.Builder, heading string) *Card

NewCard starts a card in b. A non-empty heading is written as an H2 through EscapeMdHeading; the formatter composes it, and the whole composition is escaped, so it must not begin with '#'. An empty heading writes none, for a card that continues under a heading the caller wrote, or a card a shared renderer starts on behalf of the formatter that owns the heading.

func (*Card) Bool added in v3.1.0

func (c *Card) Bool(label string, v bool)

Bool writes the flag as BoolEmoji renders it, a tick or a cross. It is for a flag whose true reads as a good thing; a negative one, such as revoked, locked or expired, is written by Card.Warn.

func (*Card) BoolPtr added in v3.1.0

func (c *Card) BoolPtr(label string, v *bool)

BoolPtr writes the flag when GitLab sent it and nothing for nil.

func (*Card) Code added in v3.1.0

func (c *Card) Code(label, value string)

Code writes the value as a code span through MdCodeSpan, for a value the reader has to copy exactly: a key, a path, a SHA, a command. Nothing inside the span is Markdown and no entity is written. A blank value writes nothing.

func (*Card) Count added in v3.1.0

func (c *Card) Count(label string, v int64)

Count writes a number only when it is not zero, for a limit or a count whose zero means GitLab did not say.

func (*Card) End added in v3.1.0

func (c *Card) End(hints ...string)

End writes the next-step hints through WriteHints and is always the last write of a card: a row written after it lands below the guidance section, where ExtractHints no longer finds one. A secret the card showed adds its hint ahead of the caller's, with the label escaped like the row that showed it, since the hint is a list item too. It is called once, on the card NewCard started; on a nested card it writes the same section.

func (*Card) Fence added in v3.1.0

func (c *Card) Fence(title, lang, body string)

Fence writes a body inside a fenced code block sized by MarkdownFencedBlock, under a heading one level below the card's own when the title is not blank: a file, a template, a job log, a diff. An empty body writes nothing, heading included.

func (*Card) Field added in v3.1.0

func (c *Card) Field(label, value string)

Field writes "- **label**: value" with both halves through the cell escaper and the guidance heading defused. A blank value writes nothing, which is how every optional field is written: the card shows what GitLab sent.

func (*Card) FieldOr added in v3.1.0

func (c *Card) FieldOr(label, value, absent string)

FieldOr writes value like Card.Field, or absent in its place when the value is blank, for a field whose absence is the answer: "never" for an expiry, "unlimited" for a quota, "-" for a slot. A blank absent writes nothing.

func (*Card) Flag added in v3.1.0

func (c *Card) Flag(emoji, label string, on bool)

Flag writes "- <emoji> **label**" when on is true and nothing otherwise, for a condition worth stating only when it holds: confidential, archived, a draft. An empty emoji writes the label alone.

func (*Card) Int added in v3.1.0

func (c *Card) Int(label string, v int64)

Int writes a number that is an answer at zero: an ID, or a count GitLab always sends.

func (c *Card) Link(label, text, url string)

Link writes MdTitleLink of text and url: a link when url is set, the escaped text alone when it is not, and nothing when both are blank. A blank text with a url links the url itself, so a link never has an empty label.

func (*Card) Markdown added in v3.1.0

func (c *Card) Markdown(label, composed string)

Markdown writes a value the formatter composed from parts that are already escaped, such as a link built by MdTitleLink beside a status word, or a code span beside a count. It is the one row whose value is written as given, which is why the escaping gate judges the call site rather than this method: the write here is a plain concatenation with no hole of its own, and a value that reaches it raw is the formatter's finding, at the line that composed it.

func (*Card) Note added in v3.1.0

func (c *Card) Note(prose string)

Note writes a paragraph of the server's own prose after the rows and sections and before the hints: a rendering note, a confirmation sentence. It is one line, with control bytes dropped and line breaks collapsed, so it can add no structure; prose GitLab wrote belongs in Card.Text, which quotes it. A blank note writes nothing.

func (*Card) Secret added in v3.1.0

func (c *Card) Secret(label, secret string)

Secret writes a value that is shown once, as Card.Code does, and records it so that Card.End adds the hint to store it, since a token or a secret GitLab returns on creation cannot be retrieved again. A blank value writes nothing and adds no hint.

func (*Card) Section added in v3.1.0

func (c *Card) Section(title string) *Card

Section writes a heading one level below the card's own, through EscapeMdHeading, and returns the card that writes the section's rows: the shape of a large nested object, a pipeline inside a merge request, a commit inside a release. Its rows start at column zero under the heading, and a section of a section goes one level deeper, to H6 at most. A blank title opens no heading and the returned card continues the same list. The fields of the object itself are written before its sections, since a row written after one lands under the section's heading.

func (*Card) Sub added in v3.1.0

func (c *Card) Sub(label string) *Card

Sub writes a label-only row and returns the card that writes the rows nested under it, indented two spaces further: the shape of a small nested object, an author or a milestone, whose fields belong beside the parent's. The nested rows continue the parent's list, so a parent row written after them follows directly.

func (*Card) Table added in v3.1.0

func (c *Card) Table(title string, columns ...string) *CardTable

Table writes a nested collection's heading, one level below the card's own and omitted when the title is blank, then the table header for columns, and returns the table that takes the rows. The table is written at column zero whatever the card's depth, and the card's next row starts after a blank line, so the table ends where its rows end. A table with no columns writes only the heading.

func (*Card) Text added in v3.1.0

func (c *Card) Text(label, body string)

Text writes prose a person typed into GitLab: a description, a note body, a commit message. A one-line body stays on the field's line, through the cell escaper with the guidance heading defused. A longer one becomes a blockquote under the label through WrapGFMBody, indented so the quote belongs to the item, and nothing in it can add a field, a heading or a list item to the card. Trailing line breaks are dropped first, so a one-line body that ends in a newline stays inline. A blank body writes nothing.

func (*Card) Time added in v3.1.0

func (c *Card) Time(label, rfc3339 string)

Time writes an RFC 3339 timestamp in the display form FormatTime gives, or nothing when it is empty. A time.Time is passed as RFC3339Ptr of it.

func (*Card) URL added in v3.1.0

func (c *Card) URL(url string)

URL writes the "URL" row, the address linked to itself, or nothing when it is empty.

func (*Card) Warn added in v3.1.0

func (c *Card) Warn(label string, on bool)

Warn is the negative-polarity flag: revoked, locked, expired, has failures. It writes "- <warning> **label**" when on, so the condition is marked with a warning sign and never with the tick BoolEmoji gives a true, which on "Revoked" reads as success.

type CardTable added in v3.1.0

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

CardTable is a nested collection inside a card: the table Card.Table opened, taking one row at a time.

func (*CardTable) Row added in v3.1.0

func (t *CardTable) Row(cells ...string)

Row writes one row of the collection. The cells are written as given, so the caller renders each one: EscapeMdTableCell for text, MdTitleLink for a link, MdCodeSpanCell for a code span, FormatTime for a timestamp. A link is a finished construct the cell escaper would turn back into text, which is why this is the one write in a card the caller escapes for, on the same terms as MarkdownTableRow, and why the escaping gate judges the call site rather than this method. A row with no cells writes nothing.

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"), and reaches the Markdown through the inline escaper a card row uses, since the branch, package or file name inside it is the caller's and GitLab's rather than the server's: unescaped, a name carrying a line break wrote structure of its own under the confirmation, and one carrying a tag reached the page as raw HTML. The structured message keeps the sentence as it reads.

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 {
	ProjectsWithWriteAccess    []DeployKeyProjectOutput `json:"projects_with_write_access"`
	ProjectsWithReadonlyAccess []DeployKeyProjectOutput `json:"projects_with_readonly_access"`
}

DeployKeyExtra is the projects a deploy key can write to or only read from, which GitLab sends when the request asks for them. client-go models the pair on InstanceDeployKey and not on ProjectDeployKey, so this is read only for the project-scoped handlers; the last use and the usage type this shape also carried retired with client-go v3.12.0, which added both to both structs.

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 card suitable for an MCP tool result, with every field the error carries as a row. The domain and action are the handler's own, compiled in; the message and the details carry GitLab's words, and the card escapes them on their rows, where they used to be written raw on four consecutive bullet-less lines that rendered as one run-on paragraph. The request ID is a code span, so a reader can copy it.

The status row carries the code and its reason phrase and no reading of them, because the message already is the reading of the whole response. A second classification of the status alone used to sit beside it, and for a 401 the two disagreed on one card: the message said GitLab had rejected the token itself, and two rows down the status said the token might be fine and the refusal a missing permission.

type DiffExtra added in v3.1.0

type DiffExtra struct {
	Collapsed     bool `json:"collapsed"`
	TooLarge      bool `json:"too_large"`
	GeneratedFile bool `json:"generated_file"`
}

DiffExtra is what Entities::Diff sends on a file diff that client-go's Diff does not model: whether GitLab collapsed the diff or left it out as too large, which is why a diff can arrive empty, and whether the file is generated. All three are exposed with no condition. The gap is recorded in docs/development/upstream-bugs.md.

func CapturedCompareDiffs added in v3.1.0

func CapturedCompareDiffs(capture *gitlabclient.ResponseCapture, decoded int) ([]DiffExtra, error)

CapturedCompareDiffs reads the same off a captured comparison, whose file diffs sit under diffs beside the commits.

func CapturedDiffs added in v3.1.0

func CapturedDiffs(capture *gitlabclient.ResponseCapture, decoded int) ([]DiffExtra, error)

CapturedDiffs reads the flags off a captured list of file diffs, one per diff in order, the count held to what the SDK decoded.

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"`
	GeneratedFile bool   `json:"generated_file"`
	Collapsed     bool   `json:"collapsed,omitempty"`
	TooLarge      bool   `json:"too_large,omitempty"`
}

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

The last three flags are what lib/api/entities/diff.rb sends beside the eight client-go's Diff models, read from the captured response (see DiffExtra); they are spelled and tagged the way a merge request's file diff publishes them.

func DiffToOutput

func DiffToOutput(d *gl.Diff, extra DiffExtra) DiffOutput

DiffToOutput converts a GitLab API gl.Diff to the MCP tool output format, with the flags the captured response carried beside it.

func (DiffOutput) Status added in v3.1.0

func (d DiffOutput) Status() string

Status names what happened to the file in the diff, and says so when GitLab sent no diff text for it, having collapsed it or found it too large, or marked the file generated: "modified (too large)" tells a reader why the diff is empty where "modified" alone reads as a change with nothing in it.

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 DiscussionMarkdown

type DiscussionMarkdown struct {
	ID    string
	Notes []NoteMarkdown
	// QuickActions is what the mutation that opened the thread reported about
	// the quick actions its first note carried, nil otherwise.
	QuickActions *QuickActionsStatusOutput
}

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 []NoteMarkdown) DiscussionMarkdown

NewDiscussionMarkdown builds a shared Markdown view model for discussion threads.

type DiscussionNoteMarkdown

type DiscussionNoteMarkdown = NoteMarkdown

DiscussionNoteMarkdown is the note of a discussion thread, which is a GitLab note like any other and is rendered by the same view model: the two used to be separate types with renderers that disagreed on the heading, the author's spelling and the body's label, and the discussion one carried no resolution state, which kept the merge request and commit discussion packages from adopting it at all.

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) FormatNote

func (r DiscussionRenderer) FormatNote(note NoteMarkdown) string

FormatNote renders a single discussion note using the renderer hints, as the note card every note tool renders.

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) AuthorUsername added in v3.1.0

func (n DiscussionThreadNoteOutput) AuthorUsername() string

AuthorUsername returns the author's username, or "" when the note carries no author object.

func (DiscussionThreadNoteOutput) MarkdownNote

func (n DiscussionThreadNoteOutput) MarkdownNote() NoteMarkdown

MarkdownNote returns the shared Markdown view model for a thread note: the author by username, and the flags and resolution state the note carries, which the view model used to leave out and the discussion card could not show.

func (DiscussionThreadNoteOutput) ResolvedByUsername added in v3.1.0

func (n DiscussionThreadNoteOutput) ResolvedByUsername() string

ResolvedByUsername returns the resolver's username, or "" when nobody has resolved the note.

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 DuoAccessRuleNamespaceOutput added in v3.1.0

type DuoAccessRuleNamespaceOutput struct {
	ID       int64  `json:"id"`
	Name     string `json:"name"`
	FullPath string `json:"full_path"`
}

DuoAccessRuleNamespaceOutput is the namespace a Duo access rule is inherited from, absent on a rule the namespace set itself.

type DuoNamespaceAccessRuleOutput added in v3.1.0

type DuoNamespaceAccessRuleOutput struct {
	ThroughNamespace *DuoAccessRuleNamespaceOutput `json:"through_namespace"`
	Features         []string                      `json:"features"`
}

DuoNamespaceAccessRuleOutput is one entry of the duo_namespace_access_rules array ee/lib/ai/feature_access_rule_transformer.rb builds. Like AISettingsOutput it is shared: the application-setting entity exposes the same array.

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 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 GraphQLClusterAgentRef added in v3.1.0

type GraphQLClusterAgentRef struct {
	Name string `json:"name"`
}

GraphQLClusterAgentRef names the cluster agent that performed a cluster image scan. The agent's own fields are the cluster agent actions' to publish.

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. It selects
	// [GraphQLQuickActionsStatusSelection] beside the note.
	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, a refusal GitLab answers at the top level is reported as that refusal, the first mutation payload error becomes "op: message", and a missing note node is reported for what GitLab means by it.

type GraphQLNoteMutationResult added in v3.1.0

type GraphQLNoteMutationResult[N any] struct {
	// Note is the note the mutation created or edited. It is nil when the
	// body held only quick actions: createNote then runs them and keeps no
	// note, and QuickActions says what they did.
	Note *N
	// QuickActions is GitLab's account of the quick actions the body
	// carried, nil when it carried none.
	QuickActions *QuickActionsStatusOutput
}

GraphQLNoteMutationResult is what a note mutation answered with.

func ExecGraphQLNoteMutation

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

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

A payload without a note is not one condition. A body holding only quick actions makes createNote run them and keep no note, and it says so in the status, which is a result and not a failure. The same body makes updateNote delete the note it was asked to edit and answer with neither note nor status (Mutations::Notes::Update::Base in GitLab), which is reported as an error that says what happened, since the call cannot answer with the edited note it promised and a caller told only that no note came back would read the commands as not applied. A refusal GitLab answers at the top level leaves the payload null, and is checked first so that neither reading is ever given to a refusal.

A payload error is a refusal, with one exception: a note GitLab kept whose every error is a quick action failure the status reports too. createNote saves the note before it applies the commands and adds a command's failure to the note's errors afterwards, so reporting that as a refusal would hide a note that exists and invite the caller to post it twice. A body of commands alone whose update failed answers the same error and status with no note, and stays a refusal: GitLab ran nothing and kept nothing.

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 GraphQLUserCoreRef added in v3.1.0

type GraphQLUserCoreRef struct {
	Username string `json:"username"`
	Name     string `json:"name"`
	WebURL   string `json:"webUrl"`
}

GraphQLUserCoreRef decodes the UserCore reference UserCoreRefSelection selects.

func (*GraphQLUserCoreRef) Output added in v3.1.0

Output converts a decoded person, and answers nil for nobody.

type GraphQLVulnerabilityLink struct {
	Name string `json:"name"`
	URL  string `json:"url"`
}

GraphQLVulnerabilityLink decodes the VulnerabilityLink VulnerabilityLinkSelection selects.

type GraphQLVulnerabilityLocation added in v3.1.0

type GraphQLVulnerabilityLocation struct {
	File                   string                               `json:"file"`
	Path                   string                               `json:"path"`
	Image                  string                               `json:"image"`
	StartLine              string                               `json:"startLine"`
	EndLine                string                               `json:"endLine"`
	BlobPath               string                               `json:"blobPath"`
	VulnerableClass        string                               `json:"vulnerableClass"`
	VulnerableMethod       string                               `json:"vulnerableMethod"`
	CrashAddress           string                               `json:"crashAddress"`
	CrashType              string                               `json:"crashType"`
	StacktraceSnippet      string                               `json:"stacktraceSnippet"`
	Hostname               string                               `json:"hostname"`
	RequestMethod          string                               `json:"requestMethod"`
	Param                  string                               `json:"param"`
	OperatingSystem        string                               `json:"operatingSystem"`
	ContainerRepositoryURL string                               `json:"containerRepositoryUrl"`
	Description            string                               `json:"description"`
	Dependency             *GraphQLVulnerableDependency         `json:"dependency"`
	KubernetesResource     *GraphQLVulnerableKubernetesResource `json:"kubernetesResource"`
}

GraphQLVulnerabilityLocation decodes the VulnerabilityLocation union. The members spell overlapping fields alike (file, startLine, dependency), so one flat struct holds whichever member GitLab sent, and a field no member of that kind carries is simply left empty.

GitLab types startLine and endLine as String on every member, and a live instance sends them quoted; an int here made the whole response fail to decode.

func (*GraphQLVulnerabilityLocation) Output added in v3.1.0

Output converts a decoded location, and answers nil for a location GitLab did not send. A location it did send is converted whatever it carries, which is how both packages read one before.

type GraphQLVulnerabilityTokenStatus added in v3.1.0

type GraphQLVulnerabilityTokenStatus struct {
	Status         string `json:"status"`
	LastVerifiedAt string `json:"lastVerifiedAt"`
	CreatedAt      string `json:"createdAt"`
	UpdatedAt      string `json:"updatedAt"`
}

GraphQLVulnerabilityTokenStatus decodes the VulnerabilityFindingTokenStatus VulnerabilityTokenStatusSelection selects.

func (*GraphQLVulnerabilityTokenStatus) Output added in v3.1.0

Output converts a decoded token status, and answers nil for none, which is what GitLab sends for every finding that exposed no token.

type GraphQLVulnerableDependency added in v3.1.0

type GraphQLVulnerableDependency struct {
	Version string                    `json:"version"`
	Package *GraphQLVulnerablePackage `json:"package"`
}

GraphQLVulnerableDependency decodes the package a dependency or container location names, and the version of it that is vulnerable.

type GraphQLVulnerableKubernetesResource added in v3.1.0

type GraphQLVulnerableKubernetesResource struct {
	Kind          string                  `json:"kind"`
	Name          string                  `json:"name"`
	Namespace     string                  `json:"namespace"`
	ContainerName string                  `json:"containerName"`
	ClusterID     string                  `json:"clusterId"`
	Agent         *GraphQLClusterAgentRef `json:"agent"`
}

GraphQLVulnerableKubernetesResource decodes the workload a cluster image scan found running the vulnerable image.

type GraphQLVulnerablePackage added in v3.1.0

type GraphQLVulnerablePackage struct {
	Name string `json:"name"`
	Path string `json:"path"`
}

GraphQLVulnerablePackage decodes the package half of a vulnerable dependency.

type GroupDetailExtra added in v3.1.0

type GroupDetailExtra struct {
	GroupExtra
	StepUpAuthRequiredOAuthProvider        string            `json:"step_up_auth_required_oauth_provider"`
	ServiceAccessTokensExpirationEnforced  *bool             `json:"service_access_tokens_expiration_enforced"`
	AISettings                             *AISettingsOutput `json:"ai_settings"`
	UniqueProjectDownloadLimit             *int64            `json:"unique_project_download_limit"`
	UniqueProjectDownloadLimitIntervalSecs *int64            `json:"unique_project_download_limit_interval_in_seconds"`
	UniqueProjectDownloadLimitAllowlist    []string          `json:"unique_project_download_limit_allowlist"`
	UniqueProjectDownloadLimitAlertlist    []int64           `json:"unique_project_download_limit_alertlist"`
	AutoBanUserOnExcessiveProjectsDownload *bool             `json:"auto_ban_user_on_excessive_projects_download"`
}

GroupDetailExtra is GroupExtra plus what only lib/api/entities/group_detail.rb adds, which is what a request for one group renders. A list row never carries these, which is why they are a type of their own rather than more fields on the one above.

func CapturedGroup added in v3.1.0

func CapturedGroup(capture *gitlabclient.ResponseCapture) (GroupDetailExtra, error)

CapturedGroup reads, off the captured answer to a request for one group, everything only that shape carries.

type GroupExtra added in v3.1.0

type GroupExtra struct {
	ShowDiffPreviewInEmail                 bool                           `json:"show_diff_preview_in_email"`
	ResourceAccessTokenNotifyInherited     *bool                          `json:"resource_access_token_notify_inherited"`
	LockResourceAccessTokenNotifyInherited bool                           `json:"lock_resource_access_token_notify_inherited"`
	DuoCoreFeaturesEnabled                 *bool                          `json:"duo_core_features_enabled"`
	AutoDuoCodeReviewEnabled               *bool                          `json:"auto_duo_code_review_enabled"`
	WebBasedCommitSigningEnabled           *bool                          `json:"web_based_commit_signing_enabled"`
	AllowPersonalSnippets                  *bool                          `json:"allow_personal_snippets"`
	BuiltInProjectTemplatesEnabled         *bool                          `json:"built_in_project_templates_enabled"`
	LockBuiltInProjectTemplatesEnabled     *bool                          `json:"lock_built_in_project_templates_enabled"`
	DuoNamespaceAccessRules                []DuoNamespaceAccessRuleOutput `json:"duo_namespace_access_rules"`
}

GroupExtra is what lib/api/entities/group.rb and its EE prepend send on a group that client-go's Group does not carry, read from the captured response (ADR-0021). Every route that renders a group renders this entity, so these are the fields a list row carries too.

The pointers are GitLab's own conditions rather than this server's caution: each is exposed behind a licensed feature, a permission or a feature flag, so an absent key and a false one are different answers. The three plain values are exposed unconditionally on the CE entity.

func CapturedGroups added in v3.1.0

func CapturedGroups(capture *gitlabclient.ResponseCapture, decoded int) ([]GroupExtra, error)

CapturedGroups reads the list-shaped fields off a list answer, one extra per group in order, the count held to what the SDK decoded.

type GroupHookExtra added in v3.1.0

type GroupHookExtra struct {
	RepositoryUpdateEvents bool  `json:"repository_update_events"`
	DuoFlowCallbackEnabled *bool `json:"duo_flow_callback_enabled"`
}

GroupHookExtra is what ee/lib/api/entities/group_hook.rb sends that client-go's GroupHook does not model. The SDK carries repository_update_events on the project hook and on the system hook and not on this one; the entity sends it on every group hook, so a plain value is the honest shape. duo_flow_callback_enabled is sent on every group hook from GitLab 19.4 (group_webhooks.md), so it is a pointer: an older instance sends no key, and that is not a hook with the setting off.

func CapturedGroupHook added in v3.1.0

func CapturedGroupHook(capture *gitlabclient.ResponseCapture) (GroupHookExtra, error)

CapturedGroupHook reads that key off the answer to a request for one hook.

func CapturedGroupHooks added in v3.1.0

func CapturedGroupHooks(capture *gitlabclient.ResponseCapture, decoded int) ([]GroupHookExtra, error)

CapturedGroupHooks reads the same off a list answer, one extra per hook in order.

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"`
	ProvisionedByProjectID      *int64     `json:"provisioned_by_project_id"`
	UnconfirmedEmail            string     `json:"unconfirmed_email"`
}

InstanceUserExtra is UserExtra plus the six 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. provisioned_by_project_id comes from lib/api/entities/user_with_admin.rb itself, with no license, and so reaches an administrator on every route presenting that entity or one inheriting it; users.md dates it to GitLab 19.3. 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 three identifiers are pointers for the reason the counts are: a license that does not carry the feature, or a release that predates the key, sends no key, and group 0 is not that. provisioned_by_project_id is also sent as null for a user no project provisioned, which reads the same way.

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 six 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 IssueBasicExtra added in v3.1.0

type IssueBasicExtra struct {
	BlockingIssuesCount *int64 `json:"blocking_issues_count"`
	StartDate           string `json:"start_date"`
	// Type is the issue type upcased. GitLab exposes the same attribute twice,
	// as issue_type through the plain expose and as type through one carrying
	// format_with: :upcase, so this is read rather than derived here: the
	// spelling is GitLab's decision, not ours.
	Type string `json:"type"`
}

IssueBasicExtra is what lib/api/entities/issue_basic.rb sends on an issue that client-go's Issue does not carry, read from the captured response (ADR-0021). Every route that renders an issue renders this entity, so these three are on a search hit and on a merge request's closing issue too.

The count is a pointer because ee/lib/ee/api/entities/issue_basic.rb gates it on the blocked-issues licensed feature, and zero blockers is not the same answer as an instance that does not count them.

func CapturedIssueBasic added in v3.1.0

func CapturedIssueBasic(capture *gitlabclient.ResponseCapture) (IssueBasicExtra, error)

CapturedIssueBasic reads, off the captured answer to a request that renders the basic issue entity, the fields client-go's Issue does not model.

func CapturedIssueBasics added in v3.1.0

func CapturedIssueBasics(capture *gitlabclient.ResponseCapture, decoded int) ([]IssueBasicExtra, error)

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

type IssueExtra added in v3.1.0

type IssueExtra struct {
	IssueBasicExtra
	EpicIID      *int64 `json:"epic_iid"`
	HasTasks     *bool  `json:"has_tasks"`
	Imported     *bool  `json:"imported"`
	ImportedFrom string `json:"imported_from"`
	Severity     string `json:"severity"`
	TaskStatus   string `json:"task_status"`
}

IssueExtra is IssueBasicExtra plus what only lib/api/entities/issue.rb adds, which is what the fifteen routes of the issues API render. A search hit and a merge request's closing issue never carry these.

has_tasks and imported are pointers for the reason the count above is: the entity sends them on every issue it renders, and a nil here means the body came from somewhere that renders less, which is a different answer from false.

func CapturedIssue added in v3.1.0

func CapturedIssue(capture *gitlabclient.ResponseCapture) (IssueExtra, error)

CapturedIssue reads everything CapturedIssueBasic reads and the six keys the full issue entity adds.

func CapturedIssues added in v3.1.0

func CapturedIssues(capture *gitlabclient.ResponseCapture, decoded int) ([]IssueExtra, error)

CapturedIssues reads the same off a list answer, one extra per issue.

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.

func CapturedKeys added in v3.1.0

func CapturedKeys(capture *gitlabclient.ResponseCapture, decoded int) ([]KeyExtra, error)

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

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
	Archived               bool
}

LabelMarkdown holds the common fields rendered for project and group labels.

Archived is here because GitLab archives a label rather than deleting it, and a card that says nothing about it shows an archived label exactly as it shows a live one: the label carried the flag on its output type and no renderer read it.

type LabelMarkdownOptions

type LabelMarkdownOptions struct {
	DetailTitle   string
	ListTitle     string
	EmptyListText string
	DetailHints   []string
	ListHints     []string
}

LabelMarkdownOptions controls label detail and list Markdown copy. The description is rendered one way for every caller, as a card row escaped through the card, so there is no switch for it: the option that let one scope escape and the other not made one entity answer the same question two ways.

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 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 {
	SharedRunnersMinutesLimit      *int64                `json:"shared_runners_minutes_limit"`
	ExtraSharedRunnersMinutesLimit *int64                `json:"extra_shared_runners_minutes_limit"`
	AdditionalPurchasedStorageSize *int64                `json:"additional_purchased_storage_size"`
	CIMinutesUsage                 *CIMinutesUsageOutput `json:"ci_minutes_usage"`
}

NamespaceExtra is the three limits GitLab's namespace entity sends to a caller who may change them, and the compute-minute usage it sends to one who may read it. client-go v3.12.0 carries the three limits, and it carries them as plain int64, where a null and a limit of zero are the same value: a null shared_runners_minutes_limit is how GitLab says there is no limit at all, so reading these from the SDK would publish "no limit" as "limited to zero minutes". The other five fields this shape used to read retired with that release, their SDK types carrying what GitLab sends. client-go models no usage at all.

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
	// QuickActions is what a note mutation reported about the quick actions
	// the body carried, nil for a note read back or a body that carried none.
	QuickActions *QuickActionsStatusOutput
}

NoteMarkdown carries the fields every GitLab note renders with, whether it stands alone or sits in a discussion thread: issue, merge request, snippet, epic and commit notes alike.

func NewDiscussionNoteMarkdown

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

NewDiscussionNoteMarkdown builds the note view model from the four fields a discussion note always carries.

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.

func (NoteOutput) AuthorUsername added in v3.1.0

func (n NoteOutput) AuthorUsername() string

AuthorUsername returns the author's username, or "" when the note carries no author object. It is the one nil guard every note formatter used to copy.

func (NoteOutput) ResolvedByUsername added in v3.1.0

func (n NoteOutput) ResolvedByUsername() string

ResolvedByUsername returns the resolver's username, or "" when nobody has resolved the note.

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 {
	Pipeline *PackagePipelineExtra `json:"pipeline"`
	Versions []PackageVersionExtra `json:"versions"`
}

PackageExtra is what GitLab's package entity sends on a package that client-go's Package does not carry, on every route that renders one. client-go v3.14.0 models the package's creator, its Conan recipe name and its other versions with their tags; what it leaves out is on the pipelines: the one that last built the package, and the one that built each of its other versions, which only a request for one package is sent. Each of them is short the keys PackagePipelineExtra names.

The entity's pipelines key is not read. lib/api/entities/package.rb renders it as the constant EMPTY_PIPELINES whatever the package, which is how GitLab deprecated it in 16.1, so there is nothing on it to complete and no count to hold a capture to.

func CapturedPackage added in v3.1.0

func CapturedPackage(capture *gitlabclient.ResponseCapture, decodedVersions int) (PackageExtra, error)

CapturedPackage reads them off the captured answer to a request for one package, one extra per other version in order, the count held to the number of other versions the SDK decoded: the extras are paired with what client-go decoded by position, and a count that differed would put one version's pipeline keys on its neighbor.

func CapturedPackages

func CapturedPackages(capture *gitlabclient.ResponseCapture, decoded int) ([]PackageExtra, error)

CapturedPackages reads the same off a page of packages, one extra per package in order, the count held to what the SDK decoded. A page carries no other versions, so the package's own pipeline is all an extra completes and the package's position is all it is paired by.

type PackagePipelineExtra added in v3.1.0

type PackagePipelineExtra struct {
	IID       int64          `json:"iid"`
	ProjectID int64          `json:"project_id"`
	Source    string         `json:"source"`
	User      UserBasicExtra `json:"user"`
}

PackagePipelineExtra is what lib/api/entities/package/pipeline.rb sends on a package's pipeline that client-go's PackagePipeline does not carry: the pipeline's iid, its project and its source, three of the eleven keys the entity exposes, and the two keys of the UserBasic who ran it that client-go's BasicUser leaves out, the zero value for a pipeline no user ran.

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 string           `json:"created_at,omitempty"`
	UpdatedAt string           `json:"updated_at,omitempty"`
	WebURL    string           `json:"web_url"`
	User      *UserBasicOutput `json:"user"`
}

PackagePipelineOutput is a pipeline lib/api/entities/package/pipeline.rb renders on a package, sent to a caller allowed to read it: the one that last built the package, or the one that built one of its other versions. client-go's PackagePipeline carries eight of its keys; iid, project_id and source, and the user's locked and public_email, come from PackagePipelineExtra. The timestamps are RFC 3339 to the second, like the package's own.

type PackageTagOutput

type PackageTagOutput struct {
	ID        int64  `json:"id"`
	PackageID int64  `json:"package_id"`
	Name      string `json:"name"`
	CreatedAt string `json:"created_at,omitempty"`
	UpdatedAt string `json:"updated_at,omitempty"`
}

PackageTagOutput is one tag pointing at a package, every key of it read from client-go's PackageTag: the one shape a tag is published in, on a package a listing or a request for one package returns and on each of its other versions alike. The timestamps are RFC 3339 to the second, as every timestamp this server publishes as a string is.

type PackageVersionExtra added in v3.1.0

type PackageVersionExtra struct {
	Pipeline *PackagePipelineExtra `json:"pipeline"`
}

PackageVersionExtra is the part of one other version of a package that client-go's PackageVersion does not carry, all of it on its pipeline.

type PackageVersionOutput

type PackageVersionOutput struct {
	ID        int64                  `json:"id"`
	Version   string                 `json:"version"`
	CreatedAt string                 `json:"created_at,omitempty"`
	Tags      []PackageTagOutput     `json:"tags,omitempty"`
	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, which GitLab sends only when one package is asked for and never on a page of them. It is published in this shape rather than decoded into it: client-go's PackageVersion carries the version, when it was published, its tags and eight keys of the pipeline that built it, and PackageExtra supplies the other five. Its tags and its timestamp are published as the package's own are.

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 on the content being read. If the tracker is inactive, the wrapper still works but skips notifications.

The message says read rather than uploaded, and that is the whole point of it. Nothing here measures a transfer: client-go's UploadRequest copies the whole multipart body into a buffer before it builds the request, and retryablehttp reads a plain io.Reader body to the end for the same reason, so every frame this emits has already fired by the time anything reaches the wire. A message that said "Uploaded" asserted something that had not happened, reached its total before the request existed, and left the phase the caller actually waits on reporting nothing.

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.

A total of zero means the size is not known, which is the ordinary case for a streamed package download: client-go's writer form does not surface the content length. The wire field is omitted for it, which is what the specification asks; the message has to leave it out too, or every frame of that download reads "Downloaded 65536 / 0 bytes".

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 ProjectApprovalRuleExtra added in v3.1.0

type ProjectApprovalRuleExtra struct {
	CoverageMinimumThreshold *int64 `json:"coverage_minimum_threshold"`
}

ProjectApprovalRuleExtra is what ee/lib/api/entities/project_approval_rule.rb sends that client-go's ProjectApprovalRule does not model. The threshold is exposed only on a rule whose report_type is code_coverage, so nil here means "not a coverage rule" rather than a threshold of zero.

func CapturedProjectApprovalRule added in v3.1.0

func CapturedProjectApprovalRule(capture *gitlabclient.ResponseCapture) (ProjectApprovalRuleExtra, error)

CapturedProjectApprovalRule reads that key off the answer to a request for one rule.

func CapturedProjectApprovalRules added in v3.1.0

func CapturedProjectApprovalRules(capture *gitlabclient.ResponseCapture, decoded int) ([]ProjectApprovalRuleExtra, error)

CapturedProjectApprovalRules reads the same off a list answer, one extra per rule in order.

type ProjectExtra added in v3.1.0

type ProjectExtra struct {
	DescriptionHTML                        *string `json:"description_html"`
	RepositoryObjectFormat                 *string `json:"repository_object_format"`
	ShowDiffPreviewInEmail                 *bool   `json:"show_diff_preview_in_email"`
	WarnAboutPotentiallyUnwantedCharacters *bool   `json:"warn_about_potentially_unwanted_characters"`
	SecretPushProtectionEnabled            *bool   `json:"secret_push_protection_enabled"`
	WebBasedCommitSigningEnabled           *bool   `json:"web_based_commit_signing_enabled"`
	// MergeTrainEnforcement is a string and not a flag: GitLab sends one of
	// allow_bypass, enforce_for_all_users and enforce_with_owner_override
	// (the live API record types the route's parameter as String). It was
	// declared a bool at first, and on a licensed instance every project
	// answer that carried the key failed to decode, which took project.create
	// down with it on the meta and dynamic surfaces.
	MergeTrainEnforcement                 *string `json:"merge_train_enforcement"`
	MaxPipelinesPerMergeTrain             *int64  `json:"max_pipelines_per_merge_train"`
	DuoRemoteFlowsEnabled                 *bool   `json:"duo_remote_flows_enabled"`
	DuoFoundationalFlowsEnabled           *bool   `json:"duo_foundational_flows_enabled"`
	OnlyAllowMergeIfAllStatusChecksPassed *bool   `json:"only_allow_merge_if_all_status_checks_passed"`
	DuoSastFPDetectionEnabled             *bool   `json:"duo_sast_fp_detection_enabled"`
	DuoSastVRWorkflowEnabled              *bool   `json:"duo_sast_vr_workflow_enabled"`
	DuoSecretDetectionFPEnabled           *bool   `json:"duo_secret_detection_fp_enabled"`
	DuoDependencyBumpBreakingChanges      *bool   `json:"duo_dependency_bump_breaking_changes_enabled"`
	SecurityPolicyPipelineMustSucceed     *bool   `json:"security_policy_pipeline_must_succeed"`
	SPPRepositoryPipelineAccess           *bool   `json:"spp_repository_pipeline_access"`
	// CISkipBranchPipelinesForMRs is one of the CI/CD settings
	// lib/api/entities/project.rb sends only to a caller holding admin_project
	// on the project, and GitLab sends it from 19.4 (projects.md).
	CISkipBranchPipelinesForMRs *bool `json:"ci_skip_branch_pipelines_for_mrs"`
}

ProjectExtra is what lib/api/entities/project.rb and its EE override send that client-go's Project does not model. Every field is a pointer because none of them is unconditional in practice: the first four are absent whenever the caller asked for the simple form, which renders BasicProjectDetails instead, and the rest are gated on a licensed feature, on an ability, or on GitLab.com.

The tiers here are read from the licensed feature each lambda names rather than from the audit's resolved tier, because gen_api_live captures a condition as source text over a line range and the text of every expose in ee/lib/ee/api/entities/project.rb runs on into the next one. Two fields are mis-tiered by that bleed: max_pipelines_per_merge_train gates on merge_trains (Premium) and reads as Ultimate from its neighbour's external_status_checks, and duo_foundational_flows_enabled gates on ai_workflows (Premium) and reads as Ultimate from ai_features.

func CapturedProject added in v3.1.0

func CapturedProject(capture *gitlabclient.ResponseCapture) (ProjectExtra, error)

CapturedProject reads, off the captured answer to a request that renders the full project entity, the keys client-go's Project does not model.

func CapturedProjects added in v3.1.0

func CapturedProjects(capture *gitlabclient.ResponseCapture, decoded int) ([]ProjectExtra, error)

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

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 QuickActionsStatusOutput added in v3.1.0

type QuickActionsStatusOutput struct {
	CommandNames  []string `json:"command_names,omitempty"`
	CommandsOnly  bool     `json:"commands_only"`
	Messages      []string `json:"messages,omitempty"`
	ErrorMessages []string `json:"error_messages,omitempty"`
}

QuickActionsStatusOutput mirrors GitLab's QuickActionsStatus, what a note mutation reports about the quick actions its body carried: the commands it recognized, whether the body held nothing else (in which case GitLab keeps no note), what the commands did, and what they failed to do. A nil pointer on an output is a body that carried none: GitLab answers a status for every note on an item that supports quick actions, an empty one when the body named no command, and the decoder reads the empty one as none.

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. The one exception is the listing bucket the whole process shares beside every entry's (see [processCatalog]).

rate.Limiter is safe for concurrent use by design, so RateLimiter does not need additional synchronization of its own for the requests it counts one at a time; takeMu is for the one bucket that counts a listing by its size.

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 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.

func ParseSafeModePreview added in v3.1.0

func ParseSafeModePreview(text string) (SafeModePreview, bool)

ParseSafeModePreview reads a preview back out of the card FormatSafeModePreviewMarkdown wrote, and reports whether the text is one: a card whose status row says blocked and whose mode row says safe. It is the one reader a test or an evaluator needs, so that none of them parses the card by hand.

type ServiceAccountExtra

type ServiceAccountExtra struct {
	PublicEmail string `json:"public_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.

Only the public email is read here, and only for the project endpoint: client-go's ProjectServiceAccount models the unconfirmed address and not the public one, while GroupServiceAccount gained both in v3.12.0 and the group package now reads them from the SDK. 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 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 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.

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 an optional third attribute column.

type TemplateAttributeListMarkdownOptions

type TemplateAttributeListMarkdownOptions struct {
	Title           string
	EmptyMessage    string
	AttributeHeader string
	Pagination      PaginationOutput
	Hints           []string
}

TemplateAttributeListMarkdownOptions configures template-style list rendering. An empty AttributeHeader renders the two-column Key/Name table the plain template families use; a named one adds the third 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
	Hints          []string
}

TemplateDetailMarkdown carries common fields for template-style detail pages. Every family renders its fields the same way, as card rows: the switch that let the license family write bullet-less label lines is gone, since consecutive lines of that shape rendered as one run-on paragraph.

type TemplateMarkdown

type TemplateMarkdown struct {
	Key  string `json:"key"`
	Name string `json:"name"`
}

TemplateMarkdown carries common fields rendered by template list tools.

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, as the two-column form of FormatTemplateAttributeListMarkdown: one table renderer serves the families with an attribute column and the ones without, so they cannot disagree on the heading, the empty form or the footer.

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"`
	Author    UserBasicExtra        `json:"author"`
}

TodoExtra is what GitLab's todo entity sends that client-go's Todo does not carry: when the todo last changed, the group it belongs to, which is present only on a todo raised in a group rather than a project, and the two keys of its author's UserBasic that client-go's BasicUser leaves out, which UserBasicFrom completes the author with.

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 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 UserBasicExtra added in v3.1.0

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

UserBasicExtra is what lib/api/entities/user_basic.rb sends that client-go's ProjectUser does not model, and neither does its BasicUser, which is what a to-do's author and the user who ran a package's pipeline decode into. Both are unconditional on that entity, so a plain value is the honest shape here.

func CapturedNestedUserBasics added in v3.1.0

func CapturedNestedUserBasics(capture *gitlabclient.ResponseCapture, decoded int) ([]UserBasicExtra, error)

CapturedNestedUserBasics reads the same two keys off a list answer whose rows nest the user rather than being one, one extra per row in order.

func CapturedUserBasics added in v3.1.0

func CapturedUserBasics(capture *gitlabclient.ResponseCapture, decoded int) ([]UserBasicExtra, error)

CapturedUserBasics reads, off the captured answer to a request that renders a page of the basic user entity, the two keys the SDK does not model, one extra per user in order.

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.

func UserBasicFrom added in v3.1.0

func UserBasicFrom(u *gl.BasicUser, extra UserBasicExtra) *UserBasicOutput

UserBasicFrom converts a user client-go decodes into BasicUser to the whole of lib/api/entities/user_basic.rb, with the two keys BasicUser does not model taken from what the captured answer carried for the same user. The seventh key BasicUser decodes, created_at, is not one UserBasic sends, so it is not published. It returns nil for a user the answer did not carry; the extra read off a null or absent user is the zero value, so nothing is lost.

type UserCoreRefOutput added in v3.1.0

type UserCoreRefOutput struct {
	Username string `json:"username"`
	Name     string `json:"name,omitempty"`
	WebURL   string `json:"web_url,omitempty"`
}

UserCoreRefOutput names a person GitLab recorded against an object, such as who confirmed, dismissed or resolved a vulnerability.

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, and reaches the Markdown through the same escaper DeleteResult uses and for the same reason.

type VulnerabilityLinkOutput added in v3.1.0

type VulnerabilityLinkOutput struct {
	Name string `json:"name,omitempty"`
	URL  string `json:"url"`
}

VulnerabilityLinkOutput is one reference the report attached. The name is optional in the report and the address is not.

func VulnerabilityLinkOutputs added in v3.1.0

func VulnerabilityLinkOutputs(links []GraphQLVulnerabilityLink) []VulnerabilityLinkOutput

VulnerabilityLinkOutputs converts the report's references, and answers nil for none so the field is left out rather than written as an empty list.

type VulnerabilityLocationOutput added in v3.1.0

type VulnerabilityLocationOutput struct {
	File                   string                              `json:"file,omitempty"`
	StartLine              int                                 `json:"start_line,omitempty"`
	EndLine                int                                 `json:"end_line,omitempty"`
	BlobPath               string                              `json:"blob_path,omitempty"`
	VulnerableClass        string                              `json:"vulnerable_class,omitempty"`
	VulnerableMethod       string                              `json:"vulnerable_method,omitempty"`
	CrashAddress           string                              `json:"crash_address,omitempty"`
	CrashType              string                              `json:"crash_type,omitempty"`
	StacktraceSnippet      string                              `json:"stacktrace_snippet,omitempty"`
	Hostname               string                              `json:"hostname,omitempty"`
	RequestMethod          string                              `json:"request_method,omitempty"`
	Param                  string                              `json:"param,omitempty"`
	OperatingSystem        string                              `json:"operating_system,omitempty"`
	ContainerRepositoryURL string                              `json:"container_repository_url,omitempty"`
	Description            string                              `json:"description,omitempty"`
	Dependency             *VulnerableDependencyOutput         `json:"dependency,omitempty"`
	KubernetesResource     *VulnerableKubernetesResourceOutput `json:"kubernetes_resource,omitempty"`
}

VulnerabilityLocationOutput is where a scanner found a vulnerability, in the terms its scan type uses: a file and a line range for SAST, secret detection and coverage fuzzing, a request for DAST, a package and a version for dependency and container scanning, a workload for a cluster image scan, and free prose for a generic scanner.

File is the subject every scan type has one of: the file, and for a location that names none, the DAST request path or the container image, which is how it read before the other members were decoded and what a caller that reads File alone still finds.

type VulnerabilityTokenStatusOutput added in v3.1.0

type VulnerabilityTokenStatusOutput struct {
	Status         string `json:"status"`
	LastVerifiedAt string `json:"last_verified_at,omitempty"`
	CreatedAt      string `json:"created_at,omitempty"`
	UpdatedAt      string `json:"updated_at,omitempty"`
}

VulnerabilityTokenStatusOutput says whether a leaked secret still works: ACTIVE, INACTIVE or UNKNOWN, as GitLab last verified it with the service that issued it. It is what decides whether a secret detection finding is an incident or a clean-up.

type VulnerableDependencyOutput added in v3.1.0

type VulnerableDependencyOutput struct {
	PackageName string `json:"package_name,omitempty"`
	PackagePath string `json:"package_path,omitempty"`
	Version     string `json:"version,omitempty"`
}

VulnerableDependencyOutput is the vulnerable package and its version.

type VulnerableKubernetesResourceOutput added in v3.1.0

type VulnerableKubernetesResourceOutput struct {
	Kind          string `json:"kind,omitempty"`
	Name          string `json:"name,omitempty"`
	Namespace     string `json:"namespace,omitempty"`
	ContainerName string `json:"container_name,omitempty"`
	AgentName     string `json:"agent_name,omitempty"`
	ClusterID     string `json:"cluster_id,omitempty"`
}

VulnerableKubernetesResourceOutput is the workload a cluster image scan found running the vulnerable image, and the agent that scanned it.

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