mcpsurface

package
v3.0.0 Latest Latest
Warning

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

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

Documentation

Overview

Package mcpsurface is the one reader of the MCP surface this server serves.

Several commands under cmd/ need the same thing: the tools, prompts and resources the server actually registers, read over a real MCP round-trip rather than described by hand, and against a surface that does not depend on the ambient environment. The server chooses its catalog from TOOL_SURFACE and its client from GITLAB_URL/GITLAB_TOKEN, so a generator that read either would emit different files on a developer machine than in CI. Every constructor here pins the surface explicitly and talks to an in-process stub instead, which is what makes the committed artifacts reproducible.

What it offers is one listing per surface — IndividualTools, MetaTools, DynamicTools, Resources and Prompts — over the offline client NewStubClient builds, each registering exactly what cmd/server registers for that surface and tier.

Three properties are the reason this is one package rather than a helper per command, and each of them was a defect in the readers it replaced:

  • Session applies the served schema chain, LockdownInputSchemas then EnrichPaginationConstraints, in the order cmd/server installs them. A listing that applies neither describes a schema no client receives.
  • [requireCompleteListing] stops a run whose listing came back truncated, rather than letting a first page be published as the whole surface.
  • Listings are memoized on (client, surface, tier, meta parameter-schema mode), because registering a full surface costs seconds and every caller only reads the result. A caller must not sort a returned slice in place.

Index

Constants

View Source
const (
	DynamicFindToolName          = "gitlab_find_action"
	DynamicExecuteActionToolName = "gitlab_execute_action"
)

The dynamic surface is the default a user gets with no configuration, and it is a fixed two-tool contract. Generators name the pair so a rename shows up as a failed generation rather than as silently different output.

View Source
const StubToken = "gen-surface-token" //#nosec G101 -- not a real credential, in-process stub only

StubToken is the dummy credential the generators authenticate their stub client with. It is never sent to a real GitLab instance: NewStubClient points the client at an in-process HTTP server. A generator must not fall back to GITLAB_TOKEN, or a machine that exports one would produce different output from a machine that does not.

Variables

This section is empty.

Functions

func DynamicCatalog

func DynamicCatalog(client *gitlabclient.Client, enterprise bool) *actioncatalog.Catalog

DynamicCatalog builds the canonical action catalog behind the dynamic find/execute surface, including the standalone actions that are not part of any domain meta-tool. enterprise selects the Premium/Ultimate catalog.

It delegates to dynamiccatalog.Build with a configuration that narrows nothing, so the generators describe the catalog the server assembles rather than a second assembly of the same parts: an unconfigured deployment is what a generated artifact must describe, and this is the function cmd/server calls to assemble it. Several audit commands and the e2e suite still put their own copy together; each one that moves onto this package is one fewer surface that can drift from the served one without a test noticing.

Assembly reads only the ActionSpecs compiled into this binary, so a failure means the committed catalog is malformed, which no generator run can fix and every caller would only print.

func DynamicTools

func DynamicTools(client *gitlabclient.Client) []*mcp.Tool

DynamicTools returns the visible two-tool dynamic catalog from a real MCP tools/list session, in find-then-execute order.

The contract check is an assertion about compiled-in registration, not about anything this run encountered: only an edit to the dynamic surface can break it, and it must abort generation rather than rewrite every artifact. ValidateDynamicToolContract stays exported so the rule itself is tested directly.

func DynamicToolsFromCatalog

func DynamicToolsFromCatalog(catalog *actioncatalog.Catalog) []*mcp.Tool

DynamicToolsFromCatalog lists the two-tool dynamic surface projected from catalog, for the callers that assemble one per tier rather than taking the enterprise one DynamicTools builds. The contract check is not applied here: it is an assertion about the surface a user gets, and a caller measuring one tier of it has already chosen the catalog.

func IndividualTools

func IndividualTools(client *gitlabclient.Client, tier edition.Tier) []*mcp.Tool

IndividualTools returns the individual surface at tier as a client receives it: what cmd/server registers for config.ToolSurfaceIndividual, listed over a real tools/list round-trip through Session's served-schema chain.

tools.RegisterAll is the server's own pair for this surface: the catalog built with IncludeMCP, projected with the standalone utilities, so the gitlab_server_* tools are in the result.

func MetaTools

func MetaTools(client *gitlabclient.Client, tier edition.Tier) []*mcp.Tool

MetaTools returns the meta surface at tier as a client receives it: what cmd/server registers for config.ToolSurfaceMeta, listed over a real tools/list round-trip through Session's served-schema chain.

The catalog is built with IncludeMCP, which is how cmd/server builds the one it registers (tools.SharedMetaCatalog, keyed with includeMCP true), so gitlab_server is present. tools.RegisterAllMeta builds without it and is therefore one tool short of the served surface: the difference that had the published meta counts saying 33 where the binary serves 34.

func NewGitLabComClient

func NewGitLabComClient() *gitlabclient.Client

NewGitLabComClient returns a client pinned to the public GitLab.com URL. The catalog registers the GitLab.com-only tools (Orbit) against it, so generated documentation can describe the full capability set rather than whatever the ambient GITLAB_URL points at.

The URL is the compiled-in default and the token is a constant, so the only thing client construction validates is already fixed at build time.

func NewStubClient

func NewStubClient() (client *gitlabclient.Client, cleanup func())

NewStubClient returns a GitLab client backed by an in-process stub that answers every request with a fixed version payload, plus the cleanup func that shuts the stub down. Catalog construction needs a client but performs no real request, so this keeps generation offline and identical on every machine.

func NewStubClientWithToken

func NewStubClientWithToken(token string) (client *gitlabclient.Client, cleanup func())

NewStubClientWithToken is NewStubClient with the caller's dummy token, for the commands that name their own. Retries are disabled: the stub answers every request in one round trip, and a command that somehow reached a failing one should say so rather than spend a backoff schedule on it.

It panics rather than returning an error. The only input client construction validates is a URL httptest allocated a line earlier, so a failure here is a programming error in this package rather than a condition a caller could handle: every caller could only print it and stop, which is what a panic already does, and nine of them were carrying an unreachable branch to say so.

func ProjectRoot

func ProjectRoot() (string, error)

ProjectRoot walks up from the working directory to the directory holding go.mod, so a generator works from anywhere in the repository.

The walk itself is cmdutil.RepositoryRoot: this used to be a second copy of the same loop, differing only in its error text, and a command that wanted nothing but the root had to import this package and with it the whole tool catalog. What stays here is the wording its callers report, which their tests assert on: the working directory is resolved here rather than passed as "." so that failure still names the step, and a walk that reaches the filesystem root is still reported as a missing project root.

func Prompts

func Prompts(client *gitlabclient.Client) []*mcp.Prompt

Prompts returns every registered MCP prompt definition over a real prompts/list round-trip.

func Resources

func Resources(client *gitlabclient.Client) ([]*mcp.Resource, []*mcp.ResourceTemplate)

Resources returns the static resources and resource templates advertised by the MCP server, including the surface-aware tool manifest template. The manifest is rendered for the dynamic surface because that is what a user gets by default.

The manifest's catalog is the enterprise one, which is the catalog behind the tools listed beside it. It used to be the Free one while the tools came from DynamicTools' enterprise catalog, which described neither deployment; the listing is unaffected either way, since the manifest is two registrations whatever it holds, but a mismatched pair is a trap for the next caller that reads the manifest's content rather than counting it.

func Session

func Session(setup func(*mcp.Server)) (session *mcp.ClientSession, cleanup func())

Session creates an in-memory MCP server+client pair, applies setup to the server, and returns the connected client session together with a cleanup function the caller must invoke.

The two schema middlewares are the chain cmd/server installs at cmd/server/main.go:1802-1808, in that order: the lockdown first, then the pagination bounds, which the comment there records must sit inside it so it sees the same finalized schema set. A listing that applies neither, or only the first, measures and documents a schema no client ever receives: without `additionalProperties: false`, with the jsonschema `,required` tag suffixes still in the descriptions, and without the page/per_page bounds.

Both ends of an in-memory transport are this process, so neither connect can fail; setup registers the catalog compiled into this binary. Reporting either as an error would add a return path to every generator that lists a surface and none of them could ever take it.

func SortDynamicTools

func SortDynamicTools(dynamicTools []*mcp.Tool)

SortDynamicTools orders the dynamic surface find-then-execute, which is the order a model should use them in, and sorts anything unexpected by name so the output stays deterministic.

func ValidateDynamicToolContract

func ValidateDynamicToolContract(dynamicTools []*mcp.Tool) error

ValidateDynamicToolContract fails when the dynamic surface is no longer exactly find plus execute, so a rename or an extra tool aborts generation instead of quietly rewriting every generated artifact.

Types

This section is empty.

Jump to

Keyboard shortcuts

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