Skip to content

Dynamic toolset

The dynamic toolset is the default low-token mode for GitLab MCP Server. It keeps the complete GitLab action catalog available, but shows your AI client only two public tools:

ToolWhat it does
gitlab_find_actionFinds the right GitLab action and returns exact parameters, examples, and safety metadata
gitlab_execute_actionExecutes the selected action after validating the action ID and parameters

Dynamic find/execute is the default tool surface. Meta-tools remain available with GITLAB_MCP_TOOL_SURFACE=meta for clients that prefer consolidated domain dispatchers.

Dynamic mode exists to keep the model’s tool context small while still reaching every GitLab operation. Large MCP servers can spend a lot of context on tool discovery before the user asks anything: GitLab MCP Server can expose up to 1,094 individual operations on GitLab.com Ultimate with Orbit, and the optional meta-tool catalog advertises 34 to 52 tools.

Dynamic mode exposes that catalog through find and execute tools instead. The model discovers only what it needs for the current task. What each surface lists in tools/list depends on the tier the instance is served at:

SurfaceFree/CEPremium (self-managed)Ultimate (self-managed)GitLab.com Ultimate with Orbit
dynamic (default)2222
meta34405152
individual8681,0221,0881,094

The two dynamic tools reach the same catalog the meta-tools dispatch to: dynamic mode changes how an action is discovered, not what it does.

tools/list

2 public dynamic tools

Find action and schema

Execute one canonical action

GitLab REST v4 or GraphQL

This usually adds one discovery call per task, but it keeps the initial MCP tool context very small. The catalog is shared with meta-tools, so dynamic mode reuses the same schemas, destructive-action classification, read-only filtering, safe-mode previews, token-scope filtering, and result formatting.

How much startup context does dynamic mode save?

Section titled “How much startup context does dynamic mode save?”

Dynamic mode costs 1,771 tokens of tool schema at startup, against 720,933 tokens for the individual surface on a GitLab Ultimate instance — a 407× reduction, because the client receives 2 tool definitions instead of 1,088.

SurfaceTierVisible toolsTool schema tokensReduction
dynamic (default)Any21,771baseline
individualFree/CE868564,299319×
individualPremium1,022680,047384×
individualUltimate1,088720,933407×

What a client loads at startup is those tool schemas plus the MCP resources and prompts, and GITLAB_MCP_CAPABILITY_SURFACE decides how many of those there are. On the default surface the total is the same on every tier, while what the two tools reach grows with it: 872 actions on Free/CE, 1,026 on Premium and 1,092 on Ultimate.

Configuration (GITLAB_MCP_TOOL_SURFACE / GITLAB_MCP_CAPABILITY_SURFACE)Tool schema tokensResources and promptsTotal at startup
dynamic / full (default)1,7719,57211,343
dynamic / minimal1,7711701,941

Methodology. Counts use the cl100k_base tokenizer (the GPT-4 / GPT-3.5 encoding) via tiktoken-go, measured against the source tree this site was built from, with the catalog built in memory and no network calls. “Tool schema tokens” covers the whole of each visible tool definition tools/list serves (name, title, description, input and output schemas, annotations), icons excluded, which is what a model pays for on every tools/list. The icons field is excluded: it holds base64 SVG data URIs for client user interfaces, and no client places one in a model’s context. MCP resources and prompts add a further 9,572 tokens under GITLAB_MCP_CAPABILITY_SURFACE=full, or 170 under minimal, in every surface alike. These figures are regenerated by make gen-footprint; the full tier × surface × schema-mode matrix is in the token footprint reference.

Enable dynamic mode by setting GITLAB_MCP_TOOL_SURFACE=dynamic (stdio) or --tool-surface=dynamic (HTTP). Dynamic mode is also the surface used when GITLAB_MCP_TOOL_SURFACE is unset, so most deployments get it by default. GITLAB_MCP_TOOL_SURFACE is the only selector: the META_TOOLS switch it replaced was removed in 3.0.0.

Add GITLAB_MCP_TOOL_SURFACE=dynamic to your server environment:

{
"servers": {
"gitlab": {
"type": "stdio",
"command": "/path/to/gitlab-mcp-server",
"env": {
"GITLAB_TOKEN": "glpat-xxxxxxxxxxxxxxxxxxxx",
"GITLAB_MCP_TOOL_SURFACE": "dynamic"
}
}
}
}
Terminal window
gitlab-mcp-server --http \
--gitlab-url=https://gitlab.com \
--tool-surface=dynamic

For the smallest startup context, also use the minimal capability surface:

Terminal window
gitlab-mcp-server --http \
--gitlab-url=https://gitlab.com \
--tool-surface=dynamic \
--capability-surface=minimal

GITLAB_MCP_CAPABILITY_SURFACE=minimal keeps the tool manifest resources (gitlab://tools and gitlab://tools/{id}), and omits optional resources, prompts, and workflow guides. Dynamic still has schema discovery because gitlab_find_action returns exact action schemas inline. GITLAB_MCP_META_PARAM_SCHEMA only affects meta-tool dispatcher schemas, so leave it at the default opaque for dynamic deployments.

The model should follow a simple rhythm: find, then execute. Dynamic mode works best when the assistant first finds an action and its exact schema, then executes the canonical domain.action ID it received.

GitLabgitlab_execute_actiongitlab_find_actionAI AssistantUserGitLabgitlab_execute_actiongitlab_find_actionAI AssistantUserList open merge requests authored by memerge request list open authored by memerge_request.list with params schema and examplesmerge_request.list with validated paramsAPI requestGitLab dataMarkdown and structured JSONFormatted answer

Each dynamic tool returns a normal MCP tool result: Markdown in content, JSON data in structuredContent, and isError on the result envelope when the server returns repair guidance. gitlab_execute_action does not use a special GitLab path. It dispatches to the same underlying action handler used by meta-tools, so schemas, policy checks, safe-mode previews, destructive classification, and result formatting stay consistent.

Each call returns a distinct payload: gitlab_find_action returns ranked candidate actions with their exact schemas, while gitlab_execute_action returns the backing handler’s actual response. Find is deliberately cheap compared with advertising every GitLab operation in tools/list — the model pays for detailed schemas only when it needs a specific action.

CallWhat the assistant receivesHow the assistant should use it
gitlab_find_actionRanked canonical action IDs with exact input_schema, backing meta-tool, domain, action, schema URI, destructive flag, required params, usage hints, examples, optional explanations, and scorePick the best domain.action candidate and build params from the schema
gitlab_execute_actionThe existing action response from the backing handler, usually Markdown plus structured JSONUse the returned data to answer the user, or repair from isError: true feedback

What an executed action returns, the Markdown and the JSON alike, is described field by field in Output Format.

gitlab_find_action takes a query, an optional limit and an optional explain. limit defaults to 20 and is capped at 50. It decides how many ranked actions come back, not how much of the catalog is searched: every search scores the whole catalog the session can reach. The confidence check reads only what comes back, though: low_confidence compares the top score with the next result returned, so under limit: 1 only the score floor applies, and the fuzzy recovery a low-confidence top result sets off can run or not depending on limit.

Its structuredContent carries the query it searched, the count of results it returned and the results, best first. Trimmed for space, one result looks like this:

{
"query": "merge request list open authored by me project",
"count": 1,
"results": [
{
"id": "merge_request.list",
"tool": "gitlab_merge_request",
"domain": "merge_request",
"action": "list",
"schema_uri": "gitlab://tools/merge_request.list",
"destructive": false,
"required_params": ["project_id"],
"input_schema": {
"type": "object",
"required": ["project_id"],
"properties": {
"project_id": { "type": ["string", "integer"] },
"state": { "type": "string" },
"scope": { "type": "string" }
}
},
"example": {
"tool": "gitlab_execute_action",
"arguments": {
"action": "merge_request.list",
"params": { "project_id": "group/project" }
}
},
"score": 275
}
]
}
FieldWhat it holds
idThe canonical action ID to pass to gitlab_execute_action
tool, domain, actionThe backing meta-tool, the catalog domain, and the action’s name inside it
schema_uriThe gitlab://tools/{id} resource that serves the same schema
destructiveWhether the action is destructive, so that execute asks for confirm: true unless GITLAB_MCP_YOLO_MODE (or AUTOPILOT) skips that step
required_paramsThe names of the parameters every call needs, which go inside params. A parameter the schema asks for only as one of several alternatives (anyOf or oneOf) is not among them: it is in required_params_any_of
required_params_any_ofPresent when the schema offers alternatives: one list of names per alternative, and a call needs every name of one list, not every list. security_attribute.update carries [["name"], ["description"], ["color"]] beside required_params ["attribute_id"], so a call sends attribute_id and at least one of the other three
input_schemaThe exact JSON Schema of params; confirm is left out of it, since it goes at the top level of the execute call. A destructive action’s schema adds x_confirmation, which names that place and says to set confirm only after the user approves, or, when GITLAB_MCP_YOLO_MODE skips the confirmation, that execute runs the action without it. gitlab://tools/{id} serves the same marker
output_schemaA best-effort JSON Schema of the result, when one is known
exampleA ready gitlab_execute_action call: its tool and arguments, with every name in required_params and the first group of required_params_any_of, and confirm: true when destructive. Each value is one the parameter’s schema accepts: a value of its enum, a date for a date, a URL for a URL, a color for a color, and Developer (30) for an access level. The identifiers and the text are stand-ins to replace before executing
scoreThe lexical relevance score
usage, parameter_guidance, related_actionsA note that tells commonly confused actions apart, binding guidance for commonly confused parameters, and curated nearby actions, when they exist
low_confidence, ambiguous_withSet on the top result when it falls below the confidence threshold, and on results that share an ambiguous alias the query used
explanationThe scoring reasons, only with explain: true

In content, the same answer is a Markdown card headed GitLab Catalog: N matching actions that repeats the query and lists the results in a table with the columns Action ID, Score, Destructive and Required Params, where the alternatives follow the required params, as in `attribute_id` and one of `name`, `description` or `color`. A Guidance column joins them when any result carries a usage note, parameter guidance or a destructive flag, and a Why column when explain is true. The card ends with the next steps: execute the chosen row now, a warning when the top result is low confidence or the alias was ambiguous, and how an execute call is shaped. A query that matched nothing gets no table: the card suggests six search terms instead, the nearby catalog tokens first and then common areas (project, issue, merge request, pipeline, branch, user) to make up the six, and those suggestions appear only in the Markdown.

The aggregate gitlab://tools manifest names the same requirements as find, split the same way, and also types them. Each manifest entry’s required_params is a list of name and type pairs such as {"name": "merge_request_iid", "type": "integer"} and holds only what every call needs; a parameter that takes several types joins them in schema order, as "string|integer" for a project ID; and required_params_any_of lists the alternative groups as such pairs, of which a call must satisfy at least one. For security_attribute.update both require attribute_id and put each of name, description and color in a group of its own, so a call needs only one of them. An entry without a type means the schema states no single plain type for that parameter, such as the value of admin.feature_set, which takes a boolean, a string or an integer: read it as any type and consult gitlab://tools/{id} for the full schema.

gitlab_execute_action takes the canonical action ID, a params object and, for a destructive action, a top-level confirm. params is required: send params: {} for an action that takes no parameters.

Before it dispatches, execute repairs the slips a model most often makes:

  • It resolves an unambiguous alias to its canonical action. An alias several actions share is refused with the canonical IDs to choose from.
  • It renames common parameter aliases to the names the selected action’s schema uses, such as mr_iid to merge_request_iid, key_id to deploy_key_id and search to query, and only when the schema takes the canonical name and not the alias.
  • It applies a few conversions scoped to one action. issue.close and issue.reopen run issue.update with the matching state_event. pipeline.schedule_create and pipeline.schedule_update take a name as the schedule’s description. A single file_name and content sent to snippet.project_create become one files entry. Access level names such as developer become GitLab’s numeric levels on actions such as project.member_add and branch.protect. A name sent to feature_flags.ff_user_list_list, which lists every user list of a project, is dropped.
  • It moves a top-level confirm: true into the action’s params, where the backing handler reads it.

Then it checks params against the action’s schema. An unknown parameter, including an unsupported security-sensitive one such as masked or protected on a pipeline schedule variable, is rejected before dispatch rather than silently removed. The error is repairable: it names the unknown parameters, with a Did you mean ...? when a valid name is close, the required parameters that are missing, and every parameter the action takes. For an action with required_params_any_of, a call that completes none of the groups is told the choice as find writes it, as in Missing one of name, description or color. for security_attribute.update, and never one alternative as if it alone were required; the groups are the ones find and gitlab://tools publish.

gitlab_execute_action/<action>: invalid params. Unknown params: ... Did you mean ...? Missing required params: ... Missing one of ... Valid params: ...

A call that passes this check and still carries a value the action cannot take is answered by the backing handler, with the same validation error it returns on the meta surface.

When an action is withheld from the session

Section titled “When an action is withheld from the session”

An action the catalog holds but this session may not run is never answered as unknown, since a model told “unknown action” concludes that the server lacks the capability. Execute names the cause instead:

  • The credential’s scopes. A read_api token is served the actions GitLab accepts from read_api, judged per action from what each sends, and the administration groups need admin_mode. A call to an action the scopes withheld is answered with the message below, which names the scopes the credential lacks for that action and who asks for each: GitLab asks api of what the action sends, and this server asks admin_mode before it serves an administration group, which GitLab does not ask of every action in it. An administration write on a read_api token is answered with both, as in GitLab requires the api scope for it, this server serves this action's group only to a credential carrying the admin_mode scope, and the credential in use does not carry them. Because GitLab accepts read_api for five actions that write, gitlab_execute_action carries readOnlyHint: false and destructiveHint: true in a read_api session, as it does for an api token.

    gitlab_execute_action: action "issue.create" exists but is not available to this session: GitLab requires the api scope for it and the credential in use does not carry it, so a narrowed action surface was built for it. Reauthorize with the api scope to use it; do not report the capability as missing.
  • The operator. A write that read-only mode removed (GITLAB_MCP_READ_ONLY=true or --read-only) is answered:

    gitlab_execute_action: action "issue.create" exists but is not available: this deployment is configured to withhold it, so a narrowed action surface was built. Ask the operator to enable it; do not report the capability as missing.
  • A fine-grained token. Execute answers right after it resolves the action, before it checks the parameters or asks for confirmation, with the permission GitLab declares or the reason no fine-grained token reaches the action. Reading a withheld answer quotes both texts.

  • The instance’s tier. An action above the tier the instance is served at is not in the catalog, and is answered with the tier it needs and the tier the session was built for. Only the operator can change the tier, so the answer asks the operator; where the tier was detected rather than set with GITLAB_MCP_TIER (--tier), it allows that the detection may be wrong, since a tier nothing answered for is Free:

    gitlab_execute_action: action "merge_train.list_project" exists but is not available: it needs GitLab Premium or Ultimate, and this instance's tier was detected as GitLab Free, so a narrowed action surface was built. If the instance is licensed for it, the detection was wrong: ask the operator to pin the tier with GITLAB_MCP_TIER or --tier. Do not report the capability as missing.

    A pinned tier is answered this deployment is configured for GitLab Free and asks the operator to configure the tier the instance has. When something else would still withhold the action once the tier served it, the same answer says so before its last sentence, so the operator is not asked to pin a tier that would only uncover the next refusal: a write on a read-only deployment adds This deployment is also read-only, which withholds the action at any tier, so the operator would have to allow writes as well., and a credential lacking a scope the action needs adds the scope and who asks for it, as in Once the tier serves it, GitLab requires the api scope for it and the credential in use does not carry it: reauthorize with the api scope as well.

An action removed by name with GITLAB_MCP_EXCLUDE_TOOLS (--exclude-tools) is never reported this way, whatever tier it needs: the operator asked for it not to exist, so execute answers it as unknown.

The related_actions a found or described action carries follow the same rule. A link to an action this session cannot run is kept when asking for it names the cause, the tier included, and dropped only when the operator excluded its target by name.

Find follows the same split. A scope narrowing or the operator’s read-only mode removes the actions from the catalog find searches. A fine-grained session searches the whole catalog and has the actions its grant does not list left out of its results before limit is applied, so the next best match takes their place, while execute still resolves them: it refuses with the reason an action the grant does not let through, and hands the rest to GitLab, which may serve one on a public project or group. A related_actions link to a withheld action is kept, since following it explains the narrowing; a link to an action excluded by name is dropped.

gitlab_find_action is more than a substring search. It indexes canonical IDs, split ID words, backing meta-tool names, domains, action names, aliases, tags, required params, optional params, schema property names, schema enum values, compact schema descriptions, and internal backend metadata.

The ranking pipeline:

  1. Normalizes the query by lower-casing it and splitting spaces, dots, underscores, and hyphens.
  2. Removes common stopwords such as the, to, with, and please.
  3. Expands synonyms such as mr → merge request, secret → CI variable/token, show → get, and remove → delete. Backend words such as github pr or jira ticket normalize to GitLab merge-request or issue concepts without exposing non-GitLab action IDs.
  4. Scores exact canonical IDs first, then aliases, tags, domain/action names, required params, schema enum values, schema fields, and broader metadata matches.
  5. Runs fuzzy typo recovery only when lexical search returns no matches or only low-confidence matches.
  6. Searches a query of five or more terms again in overlapping three- to six-term windows, so a multi-intent prompt such as discover project from remote url merge request list current user open authored surfaces both the project discovery and the merge request listing candidates.

Fuzzy recovery is bounded on purpose: it uses a bounded Levenshtein distance that allows up to two edit mistakes for tokens of at least three characters, and suppresses weak typo matches for destructive actions. That helps with prompts like merje requesy list, while short terms such as mr still rely on aliases and synonyms instead of loose typo matching.

An alias that several actions share is reported with its canonical alternatives: each result it matches carries them in ambiguous_with, and execute refuses the alias until the caller names one canonical domain.action ID.

Find accepts explain: true when the assistant needs deterministic scoring reasons. The default response stays compact. Enabling explain does not change ranking; it only adds reasoning metadata. No-match searches return a small suggestions list, and curated workflows may return related_actions, such as release.get alongside tag.get.

Some useful limits are fixed inside the server rather than configured by operators:

BehaviorCurrent value
Query lengthAt most 256 characters, published as the maxLength of the query parameter. A longer query is refused, never truncated
Results returned by findDefaults to 20 and is capped at 50
High-confidence resultScore at least 80 and at least 15 points ahead of the next result returned; with limit: 1, the floor alone
Required query termsA query of one or two meaningful terms must match all of them; a longer one may leave one unmatched, and one of four or more may leave two when it matches a multi-word tag
Long prompt handlingA query of five or more terms is also searched in overlapping three- to six-term windows, so one prompt can surface several actions
Fuzzy typo recoveryMaximum two edits, only for terms with at least three characters
Destructive fuzzy protectionA typo match to a destructive action is kept only when the query holds an exact destructive verb (delete, destroy, remove, revoke, purge) and a term naming the action’s domain, action or tag
No-match suggestionsSix suggestions: the nearby catalog tokens first, then common areas such as project, issue, merge request, pipeline, branch and user to make up the six

Those numbers are internal tuning constants. They are not environment variables. They exist to keep discovery predictable while still recovering from common model wording and typos, and the query cap keeps the cost of one search bounded.

Find only ever offers actions this server instance can route. The catalog is built for the tier the instance is served at, with the groups only GitLab.com serves, such as Orbit, added only on GitLab.com. It is then narrowed by the actions the operator excluded, the token’s scopes and read-only mode. Safe mode removes nothing: it keeps every action and turns each write into a preview.

First, find the action:

{
"tool": "gitlab_find_action",
"arguments": {
"query": "merge request list open authored by me project",
"limit": 5
}
}

Then execute it:

{
"tool": "gitlab_execute_action",
"arguments": {
"action": "merge_request.list",
"params": {
"project_id": "my-group/my-project",
"state": "opened",
"scope": "created_by_me",
"per_page": 20
}
}
}

The assistant should execute the canonical action ID returned by find. Aliases are useful for discovery, but canonical IDs are the stable execution contract.

How does dynamic mode recover from errors?

Section titled “How does dynamic mode recover from errors?”

Dynamic mode is built to be repairable. If a call returns isError: true, the assistant should treat the message as feedback and retry the correct step rather than giving up. Each failure maps to a specific recovery action.

FailureServer responseRecovery
Find query is emptyError result with example query termsRetry find with a domain, resource, verb, and useful filters
Find query over 256 charactersError result naming the query’s length and the limitSearch for one thing at a time and call find again for the next
Action ID is unknownError result, often with Did you mean ...? canonical IDsFind again or use one of the suggested canonical IDs
Action withheld from this sessionError result: action "..." exists but ... and the causeDo not report the capability as missing; tell the user the way out the result names
Ambiguous aliasError result listing the canonical IDs the alias could meanPick one listed domain.action ID from find output
Params are rejectedError result naming the unknown, missing and valid params, or the handler’s validation errorFind the action and rebuild params from input_schema
Destructive action is blockedError result saying the action is destructive and needs confirm=trueAsk the user for explicit approval, then retry with top-level confirm: true only if approved

Yes. Dynamic mode shares the destructive classification of the canonical catalog with meta-tools, and gitlab_execute_action enforces it on every call: an action classified destructive is refused unless the call carries confirm: true or the operator set GITLAB_MCP_YOLO_MODE (or AUTOPILOT, while that is unset) to a truthy value. Execute never asks the client through elicitation, so on the default surface those are the only two ways a destructive action runs, and they are the same two that skip the question on the meta and individual surfaces. A set GITLAB_MCP_YOLO_MODE decides alone, so GITLAB_MCP_YOLO_MODE=false overrides an inherited AUTOPILOT=true.

Read-only and safe mode act before that step. In read-only mode a write is not in the catalog, and execute answers it as withheld. In safe mode a write returns a preview of what it would do, without asking for confirmation and without changing anything.

{
"tool": "gitlab_execute_action",
"arguments": {
"action": "project.delete",
"params": {
"project_id": "my-group/my-project"
}
}
}

Without confirmation, and with neither setting on, the server returns an error result instead of deleting the project. To run the action intentionally, pass confirm: true at the top level of the gitlab_execute_action arguments:

{
"tool": "gitlab_execute_action",
"arguments": {
"action": "project.delete",
"confirm": true,
"params": {
"project_id": "my-group/my-project"
}
}
}

Parameters are checked before the confirmation, as What execute accepts describes, so a destructive call with a wrong parameter is refused for the parameter first, whether or not it carries confirm: true.

For safer deployments:

  • Set GITLAB_MCP_READ_ONLY=true to remove mutating actions from the catalog.
  • Set GITLAB_MCP_SAFE_MODE=true to return previews for mutating actions instead of executing them.
  • Keep GITLAB_MCP_YOLO_MODE=false and AUTOPILOT=false unless the deployment is fully trusted. With GITLAB_MCP_YOLO_MODE truthy, or unset and AUTOPILOT truthy, every action classified destructive runs without confirm: true, and so do the two calls whose arguments decide: an issue.work_item_update that empties a work item’s assignees or CRM contacts, and a project.pull_mirror_configure that would make a pull mirror overwrite diverged branches. Otherwise a destructive action is refused until the call carries confirm: true, and those two ask through elicitation or, when the client cannot be asked, refuse until the call is sent again with confirm: true.

Dynamic mode and meta-tools share one catalog, one destructive classification and the same read-only and safe-mode handling; they differ in how operations are surfaced to the model and in how a destructive action is confirmed. Dynamic mode shows two discovery/execution tools and resolves actions on demand, while meta-tools show a fixed list of domain dispatchers.

QuestionMeta-toolsDynamic toolset
What is visible in tools/list?34 to 52 tools2 public discovery/execution tools
How does the model choose?Pick a domain tool and actionFind an action with schema, then execute it
Where are schemas found?The action enum in the tool schema, gitlab://tools/{id}, or each action’s schema with GITLAB_MCP_META_PARAM_SCHEMA=compact or fullgitlab_find_action returns them inline, or gitlab://tools/{id}
Destructive confirmationconfirm: true in params or an elicitation prompt; GITLAB_MCP_YOLO_MODE and AUTOPILOT skip itTop-level confirm: true on every destructive call; GITLAB_MCP_YOLO_MODE and AUTOPILOT skip it
With the minimal capability surfaceKeeps gitlab://tools and omits optional prompts and data resourcesKeeps schema discovery through find
Typical failureA wrong domain or action choiceSkipping find, or a wrong action ID
Best current useExplicit compatibility modeDefault low-token action discovery
RollbackSet GITLAB_MCP_TOOL_SURFACE=metaDefault path
SymptomWhat to do
You only see two toolsThat is expected in dynamic mode. Ask the assistant to find actions before execution
Find returns broad resultsInclude domain, resource, action, and filters, for example merge request list open authored by me
Execute says the action is unknownFind again and execute the canonical domain.action ID from the result. An action the operator removed with --exclude-tools is answered as unknown too, since it does not exist for this deployment
Execute says the action exists but is not availableThe credential or the deployment withholds it: follow the way out the answer names, which is another scope, the operator, or a fine-grained grant that covers it. An action above the instance’s tier names the tier it needs, and only the operator can change the tier
Find never returns an action you expectOn a fine-grained token, find leaves out what the grant does not reach: read gitlab://tools/{id} for that action, whose withheld block says why. Otherwise execute it by its canonical ID, and a withheld action answers with its cause
Execute rejects parametersFind the action and retry with the exact field names and types the error and input_schema give
A destructive action returns an errorWithout confirm: true it is refused: add top-level confirm: true only after the user approves the operation. An answer that it exists but is not available means read-only mode or the token’s scopes removed it
A write returns a preview and changes nothingSafe mode is on (GITLAB_MCP_SAFE_MODE=true); the operator decides whether writes run
Resources and prompts still use contextAdd GITLAB_MCP_CAPABILITY_SURFACE=minimal or --capability-surface=minimal
Find ranks the wrong action firstName the resource and the verb, call find with explain: true to see why each result scored, and read each result’s usage note. When the top result is marked low_confidence, pick the intended row rather than the first. A phrasing that keeps missing is worth reporting as an issue

Frequently asked questions

What is the difference between gitlab_find_action and gitlab_execute_action?

gitlab_find_action and gitlab_execute_action are the two public tools of dynamic mode, and they play distinct roles. gitlab_find_action searches the canonical action catalog and returns ranked candidate actions with their exact input_schema, backing meta-tool, destructive flag, required params, usage hints, and examples — the assistant uses it to pick the best domain.action ID and build parameters. gitlab_execute_action then runs that action after validating the action ID and parameters, returning the backing handler's real response as Markdown plus structured JSON. The recommended rhythm is find, then execute: discover the schema first, then dispatch the canonical action ID.

Why does dynamic mode show only two tools?

Showing only two tools is intentional and expected in dynamic mode. Large MCP servers can spend a lot of context on tool discovery before the user asks anything; GitLab MCP Server can expose up to 1,094 individual operations on GitLab.com Ultimate with Orbit. Dynamic mode exposes that whole catalog through gitlab_find_action and gitlab_execute_action, so the initial tools/list stays tiny and the model's context budget is free for the conversation. The trade-off is one discovery call per task: the assistant should find an action before executing it.

Are destructive actions still protected in dynamic mode?

Yes. Dynamic mode shares the canonical action catalog with meta-tools, so an action is classified destructive the same way on both. gitlab_execute_action refuses every destructive action unless the call carries confirm: true or the operator set GITLAB_MCP_YOLO_MODE (or AUTOPILOT), which skip that step on every surface. Without confirmation, the server returns an error result instead of performing the action; to run it intentionally, pass confirm: true at the top level of the gitlab_execute_action arguments. Dynamic execution also validates parameters before dispatch and rejects unknown fields with repair guidance. For safer deployments, use GITLAB_MCP_READ_ONLY=true or GITLAB_MCP_SAFE_MODE=true.

How do I make dynamic mode use even less startup context?

To minimize startup context, combine dynamic mode with the minimal capability surface by adding GITLAB_MCP_CAPABILITY_SURFACE=minimal (or --capability-surface=minimal in HTTP mode). Minimal keeps the tool manifest resources gitlab://tools and gitlab://tools/{id} and omits optional resources, prompts, and workflow guides. Dynamic mode still has full schema discovery because gitlab_find_action returns exact action schemas inline. Leave GITLAB_MCP_META_PARAM_SCHEMA at its default opaque, since it only affects meta-tool dispatcher schemas and has no effect on dynamic deployments.

What is the dynamic toolset in GitLab MCP Server?

The dynamic toolset is the default low-token mode of GitLab MCP Server. It keeps the complete GitLab action catalog available while exposing only two public tools to the AI client: gitlab_find_action finds the right GitLab action and returns its exact parameters, examples, and safety metadata, and gitlab_execute_action runs the selected action after validating the action ID and parameters. The model discovers only the schemas it needs for the current task, which keeps the initial MCP tool context very small. Dynamic mode is active when GITLAB_MCP_TOOL_SURFACE is unset; set GITLAB_MCP_TOOL_SURFACE=meta to use consolidated domain meta-tools instead.

How does gitlab_find_action rank results?

gitlab_find_action is more than a substring search. It normalizes the query, removes common stopwords, expands synonyms such as mr to merge request and show to get, then scores exact canonical IDs first, followed by aliases, tags, domain and action names, required params, schema enum values, schema fields, and broader metadata. Fuzzy typo recovery runs only when lexical search returns no or low-confidence matches, allowing up to two edits for terms of at least three characters. Find returns up to 20 results (capped at 50), and treats a result as high-confidence when it scores at least 80 and leads the next result returned by at least 15 points (with limit: 1 only the floor of 80 applies).