Skip to content

refactor(tools): consolidate legacy tool names into CQRS pattern #149

@polaz

Description

@polaz

Summary

Consolidate all remaining legacy-named tools into the consistent CQRS pattern (browse_* / manage_*), improve tool descriptions for AI agent discoverability, reorganize categories, and add missing manage actions.

CRITICAL: This issue describes the FINAL STATE after completion — not intermediate steps. All documentation references (prompts, guides, llms.txt, tool pages) must use the new names and categories.

Depends On

Why blocked by docs: Documentation phases will create 30+ pages referencing current tool names. This issue must execute AFTER those pages exist, updating them in one pass to final names. Otherwise we'd create docs with legacy names and immediately rewrite them.

Current State: 8 Non-CQRS Tools

Duplicates (2 tools to DELETE)

Legacy Tool Already Exists As Evidence
create_branch manage_ref action create_branch Same params: project_id, branch, ref
list_project_members browse_members action list_project Same params: project_id, query, user_ids

Tier safety note: manage_ref displays as tier: Premium* (mixed), but its per-action gating already declares create_branch: { tier: "free", minVersion: "8.0" }. Deleting the standalone create_branch tool is safe — Free-tier users retain access via manage_ref action. Same for list_project_membersbrowse_members is tier: Free.

Renames (6 tools)

Legacy Tool New Name Actions
get_users browse_users search, get
list_todos browse_todos list (pair to existing manage_todos)
list_group_iterations browse_iterations list, get
create_group manage_namespace create (with optional parent_id for subgroups)
manage_repository manage_project create, fork, update, delete, archive, unarchive, transfer
download_attachment action download_attachment in browse_files Absorb into existing tool

Already Consolidated (no action needed)

Tool Browse Side Notes
manage_draft_notes browse_mr_discussions (actions: drafts, draft) Browse already exists within MR discussions tool

Final Tool Inventory (after consolidation)

Browse-Only Tools (5 tools)

Entity Tool Reason
commits browse_commits Immutable (no manage needed)
events browse_events Read-only activity feed
iterations browse_iterations Read-only (managed via GitLab UI)
search browse_search Utility (no write operations)
users browse_users Read-only (admin-only write)

Full CQRS Pairs (18 entities = 38 tools)

Entity Browse Manage
files browse_files manage_files
integrations browse_integrations manage_integration
labels browse_labels manage_label
members browse_members manage_member
merge_requests browse_merge_requests manage_merge_request
milestones browse_milestones manage_milestone
mr_discussions browse_mr_discussions manage_mr_discussion + manage_draft_notes
namespaces browse_namespaces manage_namespace
pipelines browse_pipelines manage_pipeline + manage_pipeline_job
projects browse_projects manage_project
refs browse_refs manage_ref
releases browse_releases manage_release
snippets browse_snippets manage_snippet
todos browse_todos manage_todos
variables browse_variables manage_variable
webhooks browse_webhooks manage_webhook
wiki browse_wiki manage_wiki
work_items browse_work_items manage_work_item

Special Tools (1)

Tool Reason
manage_context Session management, not GitLab data. No browse pair needed.

Total: 44 tools (down from 47)

  • Deleted: 3 tools (create_branch, list_project_members, download_attachment absorbed into browse_files)
  • Renamed: 5 tools (net zero change)
  • Net: 47 - 3 = 44

Implementation Plan

Phase 1: Delete Duplicates (breaking change)

  1. Remove create_branch from registry
  2. Remove list_project_members from registry
  3. Remove associated schemas and handlers
  4. Update tests that reference deleted tools

Phase 2: Renames (breaking change)

For each rename:

  1. Create new schema file (or extend existing)
  2. Create new handler (or extend existing)
  3. Register new tool in registry
  4. Remove old tool registration
  5. Update unit tests
  6. Update integration tests

get_users -> browse_users

const BrowseUsersSchema = z.discriminatedUnion("action", [
  z.object({
    action: z.literal("search").describe("Search users by name, username, or email"),
    query: z.string().describe("Search query (name, username, or email)"),
    per_page: paginationFields().per_page,
    page: paginationFields().page,
  }),
  z.object({
    action: z.literal("get").describe("Get single user by ID"),
    user_id: z.string().describe("User ID"),
  }),
]);

list_todos -> browse_todos

const BrowseTodosSchema = z.discriminatedUnion("action", [
  z.object({
    action: z.literal("list").describe("List todos with filtering"),
    state: z.enum(["pending", "done"]).optional(),
    action_type: z.enum(["assigned", "mentioned", "review_requested", ...]).optional(),
    type: z.enum(["Issue", "MergeRequest", "Epic", ...]).optional(),
    project_id: z.number().optional(),
    per_page: ..., page: ...,
  }),
]);

list_group_iterations -> browse_iterations

const BrowseIterationsSchema = z.discriminatedUnion("action", [
  z.object({
    action: z.literal("list").describe("List iterations for a group"),
    group_id: z.string().describe("Group ID or path"),
    state: z.enum(["opened", "upcoming", "current", "closed", "all"]).optional(),
    search: z.string().optional(),
    include_ancestors: z.boolean().optional(),
    per_page: ..., page: ...,
  }),
  z.object({
    action: z.literal("get").describe("Get single iteration details"),
    group_id: z.string().describe("Group ID or path"),
    iteration_id: z.string().describe("Iteration ID"),
  }),
]);

create_group -> manage_namespace

const ManageNamespaceSchema = z.discriminatedUnion("action", [
  z.object({
    action: z.literal("create").describe("Create a new group or subgroup"),
    name: z.string().describe("Group display name"),
    path: z.string().describe("URL-friendly path"),
    visibility: z.enum(["private", "internal", "public"]).describe("Group visibility level"),
    description: z.string().optional().describe("Group description"),
    parent_id: z.number().optional().describe("Parent group ID (creates subgroup when provided)"),
  }),
  z.object({
    action: z.literal("update").describe("Update group settings"),
    group_id: z.string().describe("Group ID or path"),
    name: z.string().optional().describe("New display name"),
    path: z.string().optional().describe("New URL path"),
    description: z.string().optional(),
    visibility: z.enum(["private", "internal", "public"]).optional(),
  }),
  z.object({
    action: z.literal("delete").describe("Permanently delete a group and all its projects"),
    group_id: z.string().describe("Group ID or path"),
  }),
]);

Design note: Single create action with optional parent_id instead of separate create_group/create_subgroup actions. The parent_id presence determines whether a top-level group or subgroup is created.

manage_repository -> manage_project

const ManageProjectSchema = z.discriminatedUnion("action", [
  z.object({
    action: z.literal("create").describe("Create a new project"),
    name: z.string().describe("Project name"),
    namespace: z.string().optional().describe("Group/namespace to create project in"),
    visibility: z.enum(["private", "internal", "public"]).optional(),
    description: z.string().optional(),
    initialize_with_readme: z.boolean().optional(),
  }),
  z.object({
    action: z.literal("fork").describe("Fork an existing project"),
    project_id: z.string().describe("Source project to fork"),
    namespace: z.string().optional().describe("Target namespace for fork"),
    name: z.string().optional().describe("Custom name for forked project"),
  }),
  z.object({
    action: z.literal("update").describe("Update project settings"),
    project_id: z.string().describe("Project ID or path"),
    name: z.string().optional(),
    description: z.string().optional(),
    visibility: z.enum(["private", "internal", "public"]).optional(),
    default_branch: z.string().optional(),
  }),
  z.object({
    action: z.literal("delete").describe("Permanently delete a project"),
    project_id: z.string().describe("Project ID or path"),
  }),
  z.object({
    action: z.literal("archive").describe("Archive a project (read-only, hidden from lists)"),
    project_id: z.string().describe("Project ID or path"),
  }),
  z.object({
    action: z.literal("unarchive").describe("Unarchive a project (restore to active state)"),
    project_id: z.string().describe("Project ID or path"),
  }),
  z.object({
    action: z.literal("transfer").describe("Transfer project to a different namespace"),
    project_id: z.string().describe("Project ID or path"),
    namespace: z.string().describe("Target namespace (group path or user)"),
  }),
]);

download_attachment -> browse_files action download_attachment

Add to existing BrowseFilesSchema discriminated union:

z.object({
  action: z.literal("download_attachment").describe("Download file attachment from issues/MRs"),
  project_id: z.string(),
  secret: z.string().describe("Security token from attachment URL"),
  filename: z.string().describe("Original filename"),
})

Phase 3: Tool Descriptions Rewrite (non-breaking)

Rewrite ALL tool descriptions to follow intent-first format with cross-entity disambiguation. Agent's decision tree: first sentence answers "WHEN should I use this?", then actions list, then disambiguation hints.

Description Format

[Intent sentence - when/why to use this tool]. Actions: [action list].
[Cross-reference to related tools if disambiguation needed].

New Descriptions (all 44 tools)

Projects & Repository:

Tool New Description
browse_projects Find, inspect, or list GitLab projects by name, topic, group, or visibility. Actions: search, list, get. For project members, use browse_members.
manage_project Create, fork, update, archive, or delete GitLab projects. Actions: create, fork, update, delete, archive, unarchive, transfer.
browse_namespaces Explore available groups and user namespaces for project organization. Actions: list, get, verify. Use verify to check if a path exists before creating.
manage_namespace Create, update, or delete GitLab groups and subgroups. Actions: create, update, delete. Use parent_id in create to make subgroups.
browse_commits Explore repository commit history, metadata, and code changes. Actions: list, get, diff. Use diff to see actual code changes.
browse_refs List and inspect Git branches and tags, including protection rules. Branch actions: list_branches, get_branch, list_protected_branches, get_protected_branch. Tag actions: list_tags, get_tag, list_protected_tags.
manage_ref Create, delete, and protect Git branches and tags. Branch actions: create_branch, delete_branch, protect_branch, unprotect_branch, update_branch_protection. Tag actions: create_tag, delete_tag, protect_tag (Premium), unprotect_tag (Premium).
browse_files Read repository files by path or browse directory structure. Also handles issue/MR file attachments. Actions: tree, content, download_attachment. For code SEARCH across files, use browse_search instead.
manage_files Create, update, or upload repository files via commits. Actions: single (one file), batch (multiple files atomically), upload (markdown attachments).
browse_releases List and inspect versioned software releases with changelogs and assets. Actions: list, get, assets.
manage_release Create, update, or delete releases and their asset links. Actions: create, update, delete, create_link, delete_link.

Collaboration:

Tool New Description
browse_merge_requests Find and inspect merge requests — diffs, approvals, and branch comparisons. Actions: list, get, diffs, compare, approvals (Premium).
manage_merge_request Create, update, merge, or approve merge requests. Actions: create, update, merge, approve (Premium), unapprove (Premium), get_approval_state (Premium).
browse_mr_discussions Read MR discussion threads and browse unpublished draft notes. Actions: list (threads), drafts (list drafts), draft (get single draft). For managing discussions, use manage_mr_discussion.
manage_mr_discussion Add comments, start review threads, reply, resolve, or suggest code changes on MRs. Actions: comment, thread, reply, update, resolve, suggest, apply_suggestion, apply_suggestions.
manage_draft_notes Create, edit, publish, or delete unpublished review comments on MRs. Actions: create, update, publish, publish_all, delete. Browse drafts via browse_mr_discussions (actions: drafts, draft).
browse_members List team members and their access levels in projects or groups. Actions: list_project, list_group, get_project, get_group, list_all_project (with inherited), list_all_group (with inherited).
manage_member Add, remove, or change access levels for project/group members. Actions: add_to_project, add_to_group, remove_from_project, remove_from_group, update_project, update_group.
browse_todos View your action queue — assignments, mentions, review requests, and failed pipelines. Actions: list. Filter by state, action_type, or target_type. For activity feed, use browse_events.
manage_todos Mark todos as done or restore completed ones. Actions: mark_done, mark_all_done, restore.

Planning:

Tool New Description
browse_work_items List and inspect issues, epics, tasks, and other work item types. Groups return epics, projects return issues/tasks. Actions: list, get. Use numeric ID from list results or namespace + iid from URL.
manage_work_item Create, update, or delete work items (issues, epics, tasks). Epics require GROUP namespace, Issues/Tasks require PROJECT. Actions: create, update, delete.
browse_milestones Track release planning milestones and their associated issues/MRs. Actions: list, get, issues, merge_requests, burndown (Premium).
manage_milestone Create, update, delete, or promote milestones. Actions: create, update, delete, promote (project to group).
browse_iterations List and inspect sprint/iteration cycles for agile planning. Actions: list, get. Filter by state: current, upcoming, closed.
browse_labels Find and inspect labels used for categorizing issues and MRs. Actions: list, get.
manage_label Create, update, or delete labels. Actions: create (requires name + color), update, delete.

CI/CD:

Tool New Description
browse_pipelines Monitor CI/CD pipelines, jobs, and build logs. Actions: list, get, jobs, triggers, job, logs. Use logs to fetch console output from a specific job.
manage_pipeline Trigger, retry, or cancel CI/CD pipelines. Actions: create (trigger on branch/tag), retry (re-run failed), cancel (stop running).
manage_pipeline_job Control individual pipeline jobs — trigger manual jobs, retry, or cancel. Actions: play, retry, cancel.
browse_variables List and inspect CI/CD variables for projects or groups. Actions: list, get. Supports environment scope filtering.
manage_variable Create, update, or delete CI/CD variables with environment scoping. Actions: create, update, delete.

Integrations & Content:

Tool New Description
browse_webhooks List and inspect webhook configurations for projects or groups. Actions: list, get.
manage_webhook Create, update, delete, or test webhooks for event-driven automation. Actions: create, update, delete, test.
browse_integrations List and inspect project integrations (Slack, Jira, Discord, Jenkins, etc.). Actions: list, get.
manage_integration Enable, configure, or disable project integrations. Actions: update, disable. Supports 50+ integration types.
browse_wiki Read wiki pages in projects or groups. Actions: list, get.
manage_wiki Create, update, or delete wiki pages. Actions: create, update, delete.
browse_snippets Browse reusable code snippets by scope (personal, project, public). Actions: list, get.
manage_snippet Create, update, or delete code snippets with multi-file support. Actions: create, update, delete.

Discovery:

Tool New Description
browse_search Search across GitLab — code, issues, MRs, users, commits, and more. Actions: global, project, group. Scopes: projects, issues, merge_requests, blobs (code), commits, wiki_blobs, notes. For reading a KNOWN file by path, use browse_files instead.
browse_events Track recent activity across projects — pushes, comments, merges. Actions: list_user (your activity), list_project (project feed). For actionable notifications, use browse_todos.
browse_users Find GitLab users by name, username, or email. Actions: search, get.

Session:

Tool New Description
manage_context Switch presets, profiles, or scope to control which tools and namespaces are active. Actions: show, list_presets, list_profiles, switch_preset, switch_profile, set_scope, reset.

Phase 4: Category Refactoring (non-breaking)

Reorganize tool categories for better AI agent discoverability. Currently 13 categories + "Other" dumping ground. New structure groups tools by intent.

Current Categories → New Categories

Current Tools New Category
Core (11 tools) browse_projects, browse_namespaces, browse_commits, browse_events, manage_project, browse_users Projects & Repository / Discovery
Work Items (2) browse_work_items, manage_work_item Planning
Merge Requests (5) browse_merge_requests, manage_merge_request, browse_mr_discussions, manage_mr_discussion, manage_draft_notes Collaboration
Labels (2) browse_labels, manage_label Planning
Milestones (2) browse_milestones, manage_milestone Planning
Pipelines (3) browse_pipelines, manage_pipeline, manage_pipeline_job CI/CD
Variables (2) browse_variables, manage_variable CI/CD
Files (2) browse_files, manage_files Projects & Repository
Wiki (2) browse_wiki, manage_wiki Integrations & Content
Snippets (2) browse_snippets, manage_snippet Integrations & Content
Webhooks (2) browse_webhooks, manage_webhook Integrations & Content
Integrations (2) browse_integrations, manage_integration Integrations & Content
Todos (2) browse_todos, manage_todos Collaboration
Other (8) browse_releases, manage_release, browse_refs, manage_ref, browse_members, manage_member, browse_search, manage_context Split across new categories

New Category Structure (7 categories)

Projects & Repository (11 tools)
├── browse_projects, manage_project
├── browse_namespaces, manage_namespace
├── browse_commits
├── browse_refs, manage_ref
├── browse_files, manage_files
└── browse_releases, manage_release

Collaboration (9 tools)
├── browse_merge_requests, manage_merge_request
├── browse_mr_discussions, manage_mr_discussion, manage_draft_notes
├── browse_members, manage_member
└── browse_todos, manage_todos

Planning (7 tools)
├── browse_work_items, manage_work_item
├── browse_milestones, manage_milestone
├── browse_iterations
└── browse_labels, manage_label

CI/CD (5 tools)
├── browse_pipelines, manage_pipeline, manage_pipeline_job
└── browse_variables, manage_variable

Integrations & Content (8 tools)
├── browse_webhooks, manage_webhook
├── browse_integrations, manage_integration
├── browse_wiki, manage_wiki
└── browse_snippets, manage_snippet

Discovery (3 tools)
├── browse_search
├── browse_events
└── browse_users

Session (1 tool)
└── manage_context

Total: 44 tools across 7 categories (vs current 14 categories with "Other")

Files to Update for Category Refactoring

File What to change
src/cli/list-tools.ts Rewrite ENTITY_TOOLS constant and entityOrder array (lines 356-426)
src/cli/setup/presets.ts Update TOOL_CATEGORIES array (lines 10-143) to align with new groupings
docs/tools/index.md Rewrite Tool Categories section with new groups
docs/TOOLS.md Auto-regenerates from yarn list-tools --export --toc
docs/tools/repository.md Update to match "Projects & Repository" scope
docs/tools/code-review.md Update to match "Collaboration" scope
docs/tools/project-management.md Update to match "Planning" scope
docs/tools/ci-cd.md Stays the same, already aligned
docs/.vitepress/config.mts Update sidebar /tools/ section if needed

Phase 5: New manage_project and manage_namespace Actions (minor)

Currently manage_repository only has create and fork. After rename to manage_project, add missing CRUD actions.

manage_project — New Actions

Action HTTP Method Endpoint Tier
update PUT /projects/:id Free
delete DELETE /projects/:id Free
archive POST /projects/:id/archive Free
unarchive POST /projects/:id/unarchive Free
transfer PUT /projects/:id/transfer Free

manage_namespace — New Actions

Action HTTP Method Endpoint Tier
update PUT /groups/:id Free
delete DELETE /groups/:id Free

Tier Gating for New Actions

Add to ToolAvailability.actionRequirements:

manage_project: {
  default: { tier: "free", minVersion: "8.0" },
  actions: {
    create: { tier: "free", minVersion: "8.0" },
    fork: { tier: "free", minVersion: "8.0" },
    update: { tier: "free", minVersion: "8.0" },
    delete: { tier: "free", minVersion: "8.0" },
    archive: { tier: "free", minVersion: "8.0" },
    unarchive: { tier: "free", minVersion: "12.4" },
    transfer: { tier: "free", minVersion: "11.1" },
  },
},
manage_namespace: {
  default: { tier: "free", minVersion: "8.0" },
  actions: {
    create: { tier: "free", minVersion: "8.0" },
    update: { tier: "free", minVersion: "8.0" },
    delete: { tier: "free", minVersion: "8.0" },
  },
},

Documentation Updates (CRITICAL — execute AFTER #125 and #130)

After docs phases are complete, the following files will reference legacy tool names and MUST be updated:

Auto-generated (no manual action needed)

File How it updates
docs/TOOLS.md Auto-generated by semantic-release on each release

Manual updates required

File/Directory What to update
docs/public/llms.txt Static file — manually update tool names and regenerate tool list section
docs/tools/index.md Rewrite with new categories, remove legacy names, add new tools
docs/tools/repository.md Add manage_project actions, refs, releases
docs/tools/code-review.md Update tool names if any legacy references remain
docs/tools/project-management.md Add browse_iterations, update legacy member refs
docs/tools/ci-cd.md Verify no legacy tool references
docs/prompts/**/*.md All prompt examples referencing legacy names
docs/guides/**/*.md All guide steps referencing legacy names
docs/guide/quick-start.md Tool name references
docs/cli/list-tools.md Example output (new categories visible)
CLAUDE.md Update Tool Consolidation Progress table

Note: docs/public/llms.txt is a manually-maintained static file. There is no --export-llms CLI flag. The documentation URL is https://gitlab-mcp.sw.foundation/.

Specific renames in docs (grep and replace)

Old reference New reference Affected files
create_branch manage_ref (action: create_branch) guides/, prompts/
list_project_members browse_members (action: list_project) prompts/team-onboarding.md, guides/*
get_users browse_users prompts/*, guides/team-onboarding.md
list_todos browse_todos prompts/project-management/*
list_group_iterations browse_iterations prompts/sprint-planning.md
create_group manage_namespace guides/, prompts/
manage_repository manage_project guides/, prompts/
download_attachment browse_files (action: download_attachment) guides/*

Category-related updates

File Change
src/cli/list-tools.ts Rewrite ENTITY_TOOLS + entityOrder (lines 356-426)
src/cli/setup/presets.ts Update TOOL_CATEGORIES (lines 10-143) and PRESET_DEFINITIONS
docs/.vitepress/config.mts Update /tools/ sidebar (lines 125-139)

VitePress navigation updates

Config section Changes
docs/.vitepress/config.mts sidebar Add new use-case pages if needed
Role-based tool tables Update tool names and categories in by-role pages
Use-case tool tables Update tool names in category pages

Semver Impact

MAJOR version bump — this is a breaking change:

  • 2 tools removed (agents using create_branch/list_project_members will break)
  • 6 tools renamed (agents using old names will break)
  • 1 tool absorbed (agents using download_attachment will break)
  • New actions added to existing tools (non-breaking, additive)
  • Description changes (non-breaking, cosmetic)
  • Category changes (non-breaking, cosmetic)

Consider adding deprecation warnings for 1 release before removal:

// Option: emit warning in response for 1 version, then remove
{ warning: "Tool 'create_branch' is deprecated. Use manage_ref with action 'create_branch'." }

Tests

Unit Tests

  • Remove tests for deleted tools
  • Create tests for all new/renamed schemas
  • Verify discriminated unions parse correctly
  • Verify read-only mode respects new names
  • Test new manage_project actions (update, delete, archive, unarchive, transfer)
  • Test new manage_namespace actions (update, delete)
  • Verify category assignment in list-tools output

Integration Tests

  • Verify browse_users search works (was get_users)
  • Verify browse_todos list works (was list_todos)
  • Verify browse_iterations list/get works
  • Verify manage_namespace create/update/delete works
  • Verify manage_project create/fork/update/delete/archive/unarchive/transfer works
  • Verify browse_files download_attachment action works

Regression Tests

  • Ensure manage_ref action create_branch still works after deleting standalone tool
  • Ensure browse_members action list_project still works after deleting standalone tool

Acceptance Criteria

  • Zero legacy-named tools remain (get_*, list_*, create_*, download_*)
  • All 44 tools follow browse_* / manage_* / manage_context pattern
  • All tool descriptions follow intent-first format with cross-references
  • Categories reorganized (7 intent-based vs 14 with "Other" dump)
  • manage_project supports: create, fork, update, delete, archive, unarchive, transfer
  • manage_namespace supports: create, update, delete
  • All docs pages use new tool names (zero grep hits for old names)
  • docs/TOOLS.md will auto-regenerate on next release (semantic-release)
  • docs/public/llms.txt manually updated with new tool names (static file, no CLI generation)
  • All unit tests pass
  • All integration tests pass
  • CHANGELOG clearly lists breaking changes with migration guide

Deferred (out of scope)

  • Action naming standardization (treelist_tree, userlist_user, etc.) — breaking change with high effort/low impact ratio. Defer to future issue.
  • download_attachment semantic mismatch — keeping in browse_files with disambiguation in description. Revisit if agents consistently fail to find it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions