Dynamic toolset
The dynamic toolset is the low-token mode for GitLab MCP Server. It keeps the complete GitLab action catalog available, but shows your AI client only three public tools:
| Tool | What it does |
|---|---|
gitlab_search_tools | Finds the right GitLab action from natural language, domains, verbs, aliases, and typo-tolerant matching |
gitlab_describe_tools | Returns the exact parameters, examples, and safety metadata for one or more actions |
gitlab_execute_tool | Executes the selected action after validating the action ID and parameters |
Meta-tools remain the default today because they are the most broadly compatible surface. Dynamic mode is the low-token alternative.
Why dynamic mode exists
Section titled “Why dynamic mode exists”Large MCP servers can spend a lot of context on tool discovery before the user asks anything. GitLab MCP Server can expose up to 1011 individual operations, and even the default meta-tool catalog advertises 32 to 48 domain tools.
Dynamic mode exposes that catalog through search, describe, and execute tools. The model discovers only what it needs for the current task.
flowchart LR
A[tools/list] --> B[3 public dynamic tools]
B --> C[Search action catalog]
C --> D[Describe selected actions]
D --> E[Execute one canonical action]
E --> F[GitLab REST v4 or GraphQL]
This usually adds one or two discovery calls 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 safeguards, read-only filtering, safe-mode previews, token-scope filtering, and result formatting.
Enable dynamic mode
Section titled “Enable dynamic mode”Stdio clients
Section titled “Stdio clients”Add TOOL_SURFACE=dynamic to your server environment:
{ "servers": { "gitlab": { "type": "stdio", "command": "/path/to/gitlab-mcp-server", "env": { "GITLAB_TOKEN": "glpat-xxxxxxxxxxxxxxxxxxxx", "TOOL_SURFACE": "dynamic" } } }}HTTP deployments
Section titled “HTTP deployments”gitlab-mcp-server --http \ --gitlab-url=https://gitlab.com \ --tool-surface=dynamicFor the smallest startup context, also use the minimal capability surface:
gitlab-mcp-server --http \ --gitlab-url=https://gitlab.com \ --tool-surface=dynamic \ --capability-surface=minimalThe model workflow
Section titled “The model workflow”Dynamic mode works best when the assistant follows a simple rhythm: search, describe, execute.
sequenceDiagram
participant User
participant AI as AI Assistant
participant Search as gitlab_search_tools
participant Describe as gitlab_describe_tools
participant Execute as gitlab_execute_tool
participant GitLab
User->>AI: List open merge requests authored by me
AI->>Search: merge request list open authored by me
Search-->>AI: merge_request.list
AI->>Describe: merge_request.list
Describe-->>AI: Params schema and examples
AI->>Execute: merge_request.list with validated params
Execute->>GitLab: API request
GitLab-->>Execute: GitLab data
Execute-->>AI: Markdown and structured JSON
AI-->>User: Formatted 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_tool 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 confirmations, and result formatting stay consistent.
What each call returns
Section titled “What each call returns”| Call | What the assistant receives | How the assistant should use it |
|---|---|---|
gitlab_search_tools | A ranked list of canonical action IDs with the backing meta-tool, domain, action, schema URI, destructive flag, required params, usage hints, related actions, optional explanations, and score | Pick the best domain.action candidate and describe it before execution |
gitlab_describe_tools | Exact input_schema, required params, safety metadata, usage hints, related actions, optional output_schema, schema URI, and an executable gitlab_execute_tool example | Build params from the schema and avoid guessed field names |
gitlab_execute_tool | The existing action response from the backing handler, usually Markdown plus structured JSON | Use the returned data to answer the user, or repair from isError: true feedback |
Search and describe are deliberately cheap compared with advertising every GitLab operation in tools/list. The model pays for detailed schemas only when it needs a specific action.
How search finds actions
Section titled “How search finds actions”gitlab_search_tools 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:
- Normalizes the query by lower-casing it and splitting spaces, dots, underscores, and hyphens.
- Removes common stopwords such as
the,to,with, andplease. - Expands synonyms such as
mr→ merge request,secret→ CI variable/token,show→ get, andremove→ delete. Backend words such asgithub prorjira ticketnormalize to GitLab merge-request or issue concepts without exposing non-GitLab action IDs. - Scores exact canonical IDs first, then aliases, tags, domain/action names, required params, schema enum values, schema fields, and broader metadata matches.
- Runs fuzzy typo recovery only when lexical search returns no matches or only low-confidence matches.
- Searches long prompts in three- to six-term windows so multi-step prompts can surface multiple relevant actions.
Fuzzy recovery is bounded on purpose: it 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.
Search 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 repository.compare before analyze.release_notes.
Example
Section titled “Example”First, search for the action:
{ "tool": "gitlab_search_tools", "arguments": { "query": "merge request list open authored by me project", "limit": 5 }}Then describe the selected action:
{ "tool": "gitlab_describe_tools", "arguments": { "actions": ["merge_request.list"] }}Finally, execute it:
{ "tool": "gitlab_execute_tool", "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 search or describe. Aliases are useful for discovery, but canonical IDs are the stable execution contract.
Repair behavior
Section titled “Repair behavior”Dynamic mode is built to be repairable. If a call returns isError: true, the assistant should use the message as feedback and retry the right step.
| Failure | Recovery |
|---|---|
| Search query is empty | Retry search with a domain, resource, verb, and useful filters |
| Action ID is unknown | Search again or use the suggested canonical IDs from the error message |
| Ambiguous alias | Pick one listed domain.action ID and describe it |
| Params are rejected | Describe the action and rebuild params from input_schema |
| Destructive action is blocked | Ask the user for explicit approval before retrying with top-level confirm: true |
Destructive actions stay protected
Section titled “Destructive actions stay protected”Dynamic mode reuses the same safety model as meta-tools. Destructive actions still require explicit confirmation unless your deployment has intentionally disabled prompts with YOLO_MODE or AUTOPILOT.
{ "tool": "gitlab_execute_tool", "arguments": { "action": "project.delete", "params": { "project_id": "my-group/my-project" } }}Without confirmation, 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_tool arguments:
{ "tool": "gitlab_execute_tool", "arguments": { "action": "project.delete", "confirm": true, "params": { "project_id": "my-group/my-project" } }}Dynamic execution validates parameters before dispatch. Unknown fields, including unsupported security-sensitive fields such as masked or protected on pipeline schedule variables, are rejected with repair guidance instead of being silently removed.
For safer deployments, use GITLAB_READ_ONLY=true to remove mutating actions or GITLAB_SAFE_MODE=true to preview mutations without applying them.
Dynamic vs meta-tools
Section titled “Dynamic vs meta-tools”| Question | Meta-tools | Dynamic toolset |
|---|---|---|
What is visible in tools/list? | 32 to 48 domain tools | 3 public discovery/execution tools |
| How does the model choose? | Pick a domain tool and action | Search the canonical action catalog, then describe the action |
| Where are schemas found? | Tool schema or gitlab://schema/meta/... resources | gitlab_describe_tools |
| Best current use | Default compatibility | Low-token clients and compact action discovery |
| Rollback | Already default | Unset TOOL_SURFACE or set TOOL_SURFACE=meta |
Troubleshooting
Section titled “Troubleshooting”| Symptom | What to do |
|---|---|
| You only see three tools | That is expected in dynamic mode. Ask the assistant to search and describe actions before execution |
| Search returns broad results | Include domain, resource, action, and filters, for example merge request list open authored by me |
| Execute rejects an action | Search again and use the canonical domain.action ID from the result |
| Execute rejects parameters | Describe the action and retry with the exact field names and types |
| Resources and prompts still use context | Add CAPABILITY_SURFACE=minimal or --capability-surface=minimal |