Compare commits

..

2 Commits

Author SHA1 Message Date
Michal
ec7ada5383 fix: use .[][].name in jq for wrapped JSON response
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
API returns { "resources": [...] } not bare arrays, so .[].name
produced no output. Use .[][].name to unwrap the outer object first.
Also auto-load .env in pr.sh.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 19:26:47 +00:00
b81d3be2d5 Merge pull request 'fix: use jq for completion name extraction to avoid nested matches' (#30) from fix/completion-nested-names into main
Some checks are pending
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
CI / test (push) Waiting to run
CI / build (push) Blocked by required conditions
CI / package (push) Blocked by required conditions
2026-02-23 19:23:48 +00:00
4 changed files with 28 additions and 16 deletions

View File

@@ -46,7 +46,7 @@ _mcpctl() {
# If completing the --project value # If completing the --project value
if [[ "$prev" == "--project" ]]; then if [[ "$prev" == "--project" ]]; then
local names local names
names=$(mcpctl get projects -o json 2>/dev/null | jq -r '.[].name' 2>/dev/null) names=$(mcpctl get projects -o json 2>/dev/null | jq -r '.[][].name' 2>/dev/null)
COMPREPLY=($(compgen -W "$names" -- "$cur")) COMPREPLY=($(compgen -W "$names" -- "$cur"))
return return
fi fi
@@ -55,7 +55,7 @@ _mcpctl() {
_mcpctl_resource_names() { _mcpctl_resource_names() {
local rt="$1" local rt="$1"
if [[ -n "$rt" ]]; then if [[ -n "$rt" ]]; then
mcpctl get "$rt" -o json 2>/dev/null | jq -r '.[].name' 2>/dev/null mcpctl get "$rt" -o json 2>/dev/null | jq -r '.[][].name' 2>/dev/null
fi fi
} }

View File

@@ -72,12 +72,12 @@ function __mcpctl_resource_names
if test -z "$resource" if test -z "$resource"
return return
end end
mcpctl get $resource -o json 2>/dev/null | jq -r '.[].name' 2>/dev/null mcpctl get $resource -o json 2>/dev/null | jq -r '.[][].name' 2>/dev/null
end end
# Fetch project names for --project value # Fetch project names for --project value
function __mcpctl_project_names function __mcpctl_project_names
mcpctl get projects -o json 2>/dev/null | jq -r '.[].name' 2>/dev/null mcpctl get projects -o json 2>/dev/null | jq -r '.[][].name' 2>/dev/null
end end
# --project value completion # --project value completion

17
pr.sh
View File

@@ -1,11 +1,18 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Usage: source .env && bash pr.sh "PR title" "PR body" # Usage: bash pr.sh "PR title" "PR body"
# Requires GITEA_TOKEN in environment # Loads GITEA_TOKEN from .env automatically
set -euo pipefail set -euo pipefail
GITEA_URL="http://10.0.0.194:3012" # Load .env if GITEA_TOKEN not already exported
REPO="michal/mcpctl" if [ -z "${GITEA_TOKEN:-}" ] && [ -f .env ]; then
set -a
source .env
set +a
fi
GITEA_URL="${GITEA_URL:-http://10.0.0.194:3012}"
REPO="${GITEA_OWNER:-michal}/mcpctl"
TITLE="${1:?Usage: pr.sh <title> [body]}" TITLE="${1:?Usage: pr.sh <title> [body]}"
BODY="${2:-}" BODY="${2:-}"
@@ -18,7 +25,7 @@ if [ "$HEAD" = "$BASE" ]; then
fi fi
if [ -z "${GITEA_TOKEN:-}" ]; then if [ -z "${GITEA_TOKEN:-}" ]; then
echo "Error: GITEA_TOKEN not set. Run: source .env" >&2 echo "Error: GITEA_TOKEN not set and .env not found" >&2
exit 1 exit 1
fi fi

View File

@@ -72,16 +72,19 @@ describe('fish completions', () => {
} }
}); });
it('resource name functions use jq (not regex) to avoid matching nested name fields', () => { it('resource name functions use jq .[][].name to unwrap wrapped JSON and avoid nested matches', () => {
// Regex like "name":\s*"..." on JSON matches nested server names inside project objects. // API returns { "resources": [...] } not [...], so .[].name fails silently.
// Must use jq -r '.[].name' to extract only top-level names. // Must use .[][].name to unwrap the outer object then iterate the array.
// Also must not use string match regex which matches nested name fields.
const resourceNamesFn = fishFile.match(/function __mcpctl_resource_names[\s\S]*?^end/m)?.[0] ?? ''; const resourceNamesFn = fishFile.match(/function __mcpctl_resource_names[\s\S]*?^end/m)?.[0] ?? '';
const projectNamesFn = fishFile.match(/function __mcpctl_project_names[\s\S]*?^end/m)?.[0] ?? ''; const projectNamesFn = fishFile.match(/function __mcpctl_project_names[\s\S]*?^end/m)?.[0] ?? '';
expect(resourceNamesFn, '__mcpctl_resource_names must use jq').toContain("jq -r '.[].name'"); expect(resourceNamesFn, '__mcpctl_resource_names must use jq .[][].name').toContain("jq -r '.[][].name'");
expect(resourceNamesFn, '__mcpctl_resource_names must not use string match on name').not.toMatch(/string match.*"name"/); expect(resourceNamesFn, '__mcpctl_resource_names must not use string match on name').not.toMatch(/string match.*"name"/);
// Guard against .[].name (single bracket) which fails on wrapped JSON
expect(resourceNamesFn, '__mcpctl_resource_names must not use .[].name (needs .[][].name)').not.toMatch(/jq.*'\.\[\]\.name'/);
expect(projectNamesFn, '__mcpctl_project_names must use jq').toContain("jq -r '.[].name'"); expect(projectNamesFn, '__mcpctl_project_names must use jq .[][].name').toContain("jq -r '.[][].name'");
expect(projectNamesFn, '__mcpctl_project_names must not use string match on name').not.toMatch(/string match.*"name"/); expect(projectNamesFn, '__mcpctl_project_names must not use string match on name').not.toMatch(/string match.*"name"/);
}); });
@@ -126,9 +129,11 @@ describe('bash completions', () => {
expect(bashFile).toContain('--project'); expect(bashFile).toContain('--project');
}); });
it('resource name function uses jq (not grep regex) to avoid matching nested name fields', () => { it('resource name function uses jq .[][].name to unwrap wrapped JSON and avoid nested matches', () => {
const fnMatch = bashFile.match(/_mcpctl_resource_names\(\)[\s\S]*?\n\s*\}/)?.[0] ?? ''; const fnMatch = bashFile.match(/_mcpctl_resource_names\(\)[\s\S]*?\n\s*\}/)?.[0] ?? '';
expect(fnMatch, '_mcpctl_resource_names must use jq').toContain("jq -r '.[].name'"); expect(fnMatch, '_mcpctl_resource_names must use jq .[][].name').toContain("jq -r '.[][].name'");
expect(fnMatch, '_mcpctl_resource_names must not use grep on name').not.toMatch(/grep.*"name"/); expect(fnMatch, '_mcpctl_resource_names must not use grep on name').not.toMatch(/grep.*"name"/);
// Guard against .[].name (single bracket) which fails on wrapped JSON
expect(fnMatch, '_mcpctl_resource_names must not use .[].name (needs .[][].name)').not.toMatch(/jq.*'\.\[\]\.name'/);
}); });
}); });