Documentation
¶
Overview ¶
Package toolvisibility decides which registered tool a narrowed deployment serves, for every caller that registers a tool surface the way the server does.
Read-only mode, safe mode, --exclude-tools and the narrowing of a credential carrying read_api and not api act in two places. The catalog-backed tools receive them per action from the catalog filter, inside the catalog every assembler shares. The tools registered outside the catalog, the gitlab_interactive_* flows the meta and individual surfaces register, receive them from Apply, a pass over the tools a server holds once registration is done: it removes the tools --exclude-tools names, by their registered name or, for a standalone utility, by any spelling the catalog's exclusion rule accepts; for a read_api credential it removes the standalone utilities whose action GitLab answers only from api, by the same per-action rule the catalog applies (register row AUT-001); in read-only mode it removes every tool without a read-only hint; and in safe mode it wraps what is left with previews, exempting the catalog-backed dispatchers because they already preview per action.
It is a package of its own for the reason internal/tools/dynamiccatalog is: the pass needs the two filters internal/tools holds and the two tool names internal/tools/dynamic declares, and neither package can hold it without an import cycle in its tests.
It was moved out of cmd/server when a second caller needed it: the surface evaluator that used to live under cmd registered its own server, so the pass ran nowhere but the binary and a read-only or safe-mode meta evaluation scored a surface on which gitlab_interactive_issue_create still created the issue, while the product would have withdrawn or previewed it. That caller is gone and the defect with it, because the evaluation under test/e2e drives the real binary: the pass runs in cmd/server, where it always ran, and an evaluation now sees exactly the surface a client sees because it is the same process.
What keeps it here rather than folded back is the other half, which the second caller only made visible: cmd/server is the one package exempt from the coverage rule, so while the pass lived there it had no unit test of its own on its policy. Here it has one.
What a fine-grained session is listed ¶
The package also holds the per-request half of that decision for a fine-grained personal access token (issue 952, register row AUT-007), for the same two reasons: the e2e harness has to expect what the binary serves rather than a copy of its rule, and cmd/server keeps only thin middlewares. ToolActions says which catalog actions each registered tool runs, computed once per server; ListingMiddleware narrows a fine-grained session's tools/list to the tools whose actions it may run, and CallMiddleware answers its call to a withheld tool before the SDK decodes the arguments. Both read the authority the request's client carries, so a classic session passes through them untouched.
Index ¶
- func Apply(ctx context.Context, server *mcp.Server, cfg *config.ServerConfig, ...)
- func CallMiddleware(actions func() *ToolActions) mcp.Middleware
- func ListingMiddleware(actions func() *ToolActions) mcp.Middleware
- type ToolActions
- func (t *ToolActions) ActionIDs() map[string][]string
- func (t *ToolActions) CallAction(name string, arguments json.RawMessage) (string, bool)
- func (t *ToolActions) Filter(authority *finegrained.Authority, tools []*mcp.Tool) []*mcp.Tool
- func (t *ToolActions) Listed(authority *finegrained.Authority, name string) bool
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Apply ¶
func Apply(ctx context.Context, server *mcp.Server, cfg *config.ServerConfig, toolSurface string, surfaceCatalog *actioncatalog.Catalog)
Apply narrows the tools registered on server to what cfg says the deployment serves, in four steps: the tools cfg.ExcludeTools names are removed, then, for a credential carrying read_api and not api, the standalone utilities whose action GitLab refuses read_api, then read-only mode removes every tool without a read-only hint, or safe mode wraps every mutating tool that the catalog does not already preview per action. Read-only mode wins over safe mode, since nothing is left for safe mode to wrap.
It runs after registration, over every tool the server holds, which is what reaches the tools registered outside the catalog: the catalog filter has already applied the same configuration to the catalog-backed ones, per action, and this pass is the second mechanism the standalone flows need. The exclusions reach those flows by the catalog's own rule, so an entry naming one by its group name or its canonical action ID removes it here as its tool name does (see removeExcluded). toolSurface and surfaceCatalog decide the safe-mode exemptions (see catalogBackedToolNames); a surface whose exclusions were applied to the catalog legitimately has nothing left for the first step to remove.
The token-scope filter is deliberately absent, the standalone share of the read_api narrowing aside. It is applied to the catalog, before registration, by the three catalog assemblers, which is the only place it can reach the individual surface: its keys are meta-tool group names and that surface registers one tool per action, so a pass over registered names here matched nothing and left every admin tool listed for a token with no admin_mode. See gitlabtools.MetaToolScopes. The read_api step here names tools by the rule the catalog applies (gitlabtools.StandaloneToolsBeyond), never by their annotation, which is the operator's read-only rule.
func CallMiddleware ¶ added in v3.2.0
func CallMiddleware(actions func() *ToolActions) mcp.Middleware
CallMiddleware answers a fine-grained session's call to a tool whose action it may not run with the reason, before the SDK decodes or validates the call's arguments, so one cause gets one refusal whatever the arguments (INV-012). The refusal is a tool result returned in the dispatcher's place, so it carries the resultType the call's revision requires, which the SDK adds only to what its own dispatcher answers (toolutil.LabelForRevision). Every other call passes through, and an action this cannot name from the call is left to the dispatcher, which checks the action it runs again (toolutil.FineGrainedRefusal).
actions returns nil until registration is done, which lets a call through to the dispatcher's check.
func ListingMiddleware ¶ added in v3.2.0
func ListingMiddleware(actions func() *ToolActions) mcp.Middleware
ListingMiddleware narrows a fine-grained session's tools/list to the tools ToolActions.Listed lists, and passes every other listing through as the SDK built it.
It builds a new result rather than changing the SDK's: a copy of the result with its tools replaced, so the cursor, the cache scope and _meta survive, and the tools themselves are the SDK's own, so the schemas the server finalized on them are what is served. A listing the server makes of itself is never narrowed (toolutil.IsInternalInspection): registration counts, narrows and snapshots the whole surface it registered.
actions returns nil until registration is done, which leaves a listing unfiltered; the readiness gate holds every listing back until then, and a middleware that reads after the inner chain answered, as this one does, reads after the gate opened.
Types ¶
type ToolActions ¶ added in v3.2.0
type ToolActions struct {
// contains filtered or unexported fields
}
ToolActions is, for one registered tool surface, which catalog actions each tool runs: the one action of an individual or standalone tool, and every action of a meta tool's group, with the routes its action argument is resolved through. The dynamic surface's two tools run no action of their own and are not in it.
It is what a fine-grained session's tools/list is narrowed by and what a call to a registered tool is checked against before the SDK decodes its arguments (issue 952). It is computed once per server, when registration is done, from the catalog the server registered, so it is shared by every credential the server serves and holds nothing of any of them: what differs per credential is the authority its client carries, which every read takes from the request.
func NewToolActions ¶ added in v3.2.0
func NewToolActions(surface string, catalog *actioncatalog.Catalog) *ToolActions
NewToolActions indexes the tools a server registered for surface from the catalog it registered them from.
An individual tool is mapped to the action registration bound it to, which the served call identifier answers: an individual tool name can be declared by several actions and only the first in registration order runs under it. The standalone utilities the meta and individual surfaces register beside their catalogs are mapped by their tool names. On the dynamic surface nothing is mapped, since a call names its action through gitlab_execute_action, which decides in its own handler.
func (*ToolActions) ActionIDs ¶ added in v3.2.0
func (t *ToolActions) ActionIDs() map[string][]string
ActionIDs returns, for each tool that runs catalog actions, their canonical IDs, sorted, which is what the gitlab://tools manifest narrows a fine-grained session's read by. The map is the caller's to keep.
func (*ToolActions) CallAction ¶ added in v3.2.0
func (t *ToolActions) CallAction(name string, arguments json.RawMessage) (string, bool)
CallAction returns the canonical ID of the action a call to the tool name with arguments runs, and false when the tool runs no catalog action or the call names none this resolves.
A meta tool's action argument is read alone ([actionArgument]) and resolved through the group's routes the way the meta handler resolves it, its historical spellings included; the rewrite that depends on the call's other parameters (an environment read by name) is the handler's, which checks the action it finally runs again. An argument that resolves to nothing is left to the handler, which answers it as the unknown action it is.
func (*ToolActions) Filter ¶ added in v3.2.0
func (t *ToolActions) Filter(authority *finegrained.Authority, tools []*mcp.Tool) []*mcp.Tool
Filter returns tools without the ones authority is not listed, in their order, or tools itself when it removes none.
func (*ToolActions) Listed ¶ added in v3.2.0
func (t *ToolActions) Listed(authority *finegrained.Authority, name string) bool
Listed reports whether a session holding authority is listed the tool name: an individual or standalone tool when its action is, a meta tool when any action of its group is, and a tool that runs no catalog action always. A nil authority, which every session but a fine-grained one has, lists every tool.