feat: context-aware completions with dynamic resource names
Some checks failed
CI / lint (pull_request) Has been cancelled
CI / typecheck (pull_request) Has been cancelled
CI / test (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / package (pull_request) Has been cancelled

- Hide attach-server/detach-server from --help (only relevant with --project)
- --project shows only project-scoped commands in tab completion
- Tab after resource type fetches live resource names from API
- --project value auto-completes from existing project names
- Stop offering resource types after one is already selected

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michal
2026-02-23 19:08:29 +00:00
parent ce21db3853
commit 6d2e3c2eb3
3 changed files with 183 additions and 43 deletions

View File

@@ -3,12 +3,65 @@ _mcpctl() {
_init_completion || return
local commands="status login logout config get describe delete logs create edit apply backup restore help"
local global_opts="-v --version --daemon-url --direct -h --help"
local project_commands="attach-server detach-server get describe delete logs create edit help"
local global_opts="-v --version --daemon-url --direct --project -h --help"
local resources="servers instances secrets templates projects users groups rbac"
case "${words[1]}" in
# Check if --project was given
local has_project=false
local i
for ((i=1; i < cword; i++)); do
if [[ "${words[i]}" == "--project" ]]; then
has_project=true
break
fi
done
# Find the first subcommand (skip --project and its argument, skip flags)
local subcmd=""
local subcmd_pos=0
for ((i=1; i < cword; i++)); do
if [[ "${words[i]}" == "--project" || "${words[i]}" == "--daemon-url" ]]; then
((i++)) # skip the argument
continue
fi
if [[ "${words[i]}" != -* ]]; then
subcmd="${words[i]}"
subcmd_pos=$i
break
fi
done
# Find the resource type after get/describe/delete/edit
local resource_type=""
if [[ -n "$subcmd_pos" ]] && [[ $subcmd_pos -gt 0 ]]; then
for ((i=subcmd_pos+1; i < cword; i++)); do
if [[ "${words[i]}" != -* ]] && [[ " $resources " == *" ${words[i]} "* ]]; then
resource_type="${words[i]}"
break
fi
done
fi
# If completing the --project value
if [[ "$prev" == "--project" ]]; then
local names
names=$(mcpctl get projects -o json 2>/dev/null | grep -oP '"name":\s*"\K[^"]+')
COMPREPLY=($(compgen -W "$names" -- "$cur"))
return
fi
# Fetch resource names dynamically
_mcpctl_resource_names() {
local rt="$1"
if [[ -n "$rt" ]]; then
mcpctl get "$rt" -o json 2>/dev/null | grep -oP '"name":\s*"\K[^"]+'
fi
}
case "$subcmd" in
config)
if [[ $cword -eq 2 ]]; then
if [[ $((cword - subcmd_pos)) -eq 1 ]]; then
COMPREPLY=($(compgen -W "view set path reset claude-generate impersonate help" -- "$cur"))
fi
return ;;
@@ -20,35 +73,29 @@ _mcpctl() {
return ;;
logout)
return ;;
get)
if [[ $cword -eq 2 ]]; then
get|describe|delete)
if [[ -z "$resource_type" ]]; then
COMPREPLY=($(compgen -W "$resources" -- "$cur"))
else
COMPREPLY=($(compgen -W "-o --output -h --help" -- "$cur"))
fi
return ;;
describe)
if [[ $cword -eq 2 ]]; then
COMPREPLY=($(compgen -W "$resources" -- "$cur"))
else
COMPREPLY=($(compgen -W "-o --output --show-values -h --help" -- "$cur"))
fi
return ;;
delete)
if [[ $cword -eq 2 ]]; then
COMPREPLY=($(compgen -W "$resources" -- "$cur"))
local names
names=$(_mcpctl_resource_names "$resource_type")
COMPREPLY=($(compgen -W "$names -o --output -h --help" -- "$cur"))
fi
return ;;
edit)
if [[ $cword -eq 2 ]]; then
if [[ -z "$resource_type" ]]; then
COMPREPLY=($(compgen -W "servers projects" -- "$cur"))
else
local names
names=$(_mcpctl_resource_names "$resource_type")
COMPREPLY=($(compgen -W "$names -h --help" -- "$cur"))
fi
return ;;
logs)
COMPREPLY=($(compgen -W "--tail --since -f --follow -h --help" -- "$cur"))
return ;;
create)
if [[ $cword -eq 2 ]]; then
if [[ $((cword - subcmd_pos)) -eq 1 ]]; then
COMPREPLY=($(compgen -W "server secret project user group rbac help" -- "$cur"))
fi
return ;;
@@ -61,13 +108,23 @@ _mcpctl() {
restore)
COMPREPLY=($(compgen -W "-i --input -p --password -c --conflict -h --help" -- "$cur"))
return ;;
attach-server|detach-server)
local names
names=$(_mcpctl_resource_names "servers")
COMPREPLY=($(compgen -W "$names" -- "$cur"))
return ;;
help)
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
return ;;
esac
if [[ $cword -eq 1 ]]; then
COMPREPLY=($(compgen -W "$commands $global_opts" -- "$cur"))
# No subcommand yet — offer commands based on context
if [[ -z "$subcmd" ]]; then
if $has_project; then
COMPREPLY=($(compgen -W "$project_commands $global_opts" -- "$cur"))
else
COMPREPLY=($(compgen -W "$commands $global_opts" -- "$cur"))
fi
fi
}