---
slug: "sentry-cli-multi-profile-zsh-wrapper"
title: "Skill + CLI over MCP: Replacing Sentry MCP with a multi-profile CLI wrapper"
description: "Since sentry-cli lacks native multi-profile support, I created a zsh wrapper function that enables AWS CLI-style --profile switching. Also registered it as a Claude Code skill for seamless AI agent integration."
url: "https://www.ytyng.com/en/blog/sentry-cli-multi-profile-zsh-wrapper"
publish_date: "2026-02-22T03:38:29.138Z"
created: "2026-02-22T03:38:29.139Z"
updated: "2026-02-27T11:57:07.120Z"
categories: []
keywords: ""
featured_image_url: "https://media.ytyng.com/resize/20260226/9eaf38c3ee1a48639aa93a16efc03f1c.png.webp?width=768"
has_video: true
has_music: true
video_urls: ["https://media.ytyng.net/ytyng-blog/336/featured-video-1.mp4", "https://media.ytyng.net/ytyng-blog/336/featured-video-2.mp4", "https://media.ytyng.net/ytyng-blog/336/featured-video-3.mp4"]
music_urls: ["https://media.ytyng.net/ytyng-blog/336/featured-music-336-1.mp3", "https://media.ytyng.net/ytyng-blog/336/featured-music-336-2.mp3"]
lang: "en"
---

# Skill + CLI over MCP: Replacing Sentry MCP with a multi-profile CLI wrapper

## Background

I wanted to use Sentry's CLI tool [`sentry-cli`](https://docs.sentry.io/cli/) across multiple organizations, but it lacks native multi-profile support like AWS CLI's `--profile`.

### Installing sentry-cli

```bash
brew install getsentry/tools/sentry-cli
```

### Configuration Priority

According to the [official docs](https://docs.sentry.io/cli/configuration/), configuration is loaded in this priority:

1. Command-line arguments (`-o`, `-p`, `--auth-token`)
2. Environment variables (`SENTRY_AUTH_TOKEN`, `SENTRY_ORG`, `SENTRY_PROJECT`)
3. `.sentryclirc` file (searched upward from current directory)
4. `~/.sentryclirc` (global default)

The `.sentryclirc` filename is fixed — renamed files like `.sentryclirc-staging` won't be loaded ([ref](https://github.com/getsentry/sentry-javascript/discussions/7638)).

## What About Sentry MCP Server?

Sentry provides an [MCP server](https://docs.sentry.io/product/sentry-mcp/) for direct AI agent integration with Sentry. However, it has several drawbacks:

- **The legacy version (stdio transport + Auth Token) has been effectively superseded**. The [sentry-mcp-stdio](https://github.com/getsentry/sentry-mcp-stdio) repository remains as "educational purposes", but the [remote MCP server](https://github.com/getsentry/sentry-mcp) is now recommended
- **The new version (OAuth + Streamable HTTP) requires re-authentication whenever the session expires**. You need to go through the OAuth flow in the browser again, which interrupts your workflow
- **It consumes a lot of context**. Sentry event and issue data has complex structures, and even the tool definitions alone use significant tokens
- **No multi-account support**. You can work around this by creating multiple MCP server instances, but context consumption scales linearly with the number of instances

In the end, a CLI wrapper + AI agent skill is a lighter and more flexible approach.

## Solution: zsh Wrapper Function

A zsh function always takes priority over a same-named binary in PATH, so defining a function called `sentry-cli` automatically wraps the brew-installed binary.

### Profile Storage

```bash
~/.config/sentry-cli/profiles/<name>.env
```

Simple env file format:

```bash
SENTRY_AUTH_TOKEN=sntrys_xxx...
SENTRY_ORG=my-org
SENTRY_URL=https://my-org.sentry.io/
```

`SENTRY_PROJECT` should not be fixed in the profile — specify it with `-p` at runtime since orgs typically contain multiple projects.

### The Wrapper Function

Add this to your `~/.zshrc` or shell aliases file:

```bash
function sentry-cli () {
  local _sentry_cli
  # whence -p: skip functions, return only binary paths
  _sentry_cli=$(whence -p sentry-cli) || { echo "sentry-cli not found" >&2; return 1; }
  local profile_dir="${HOME}/.config/sentry-cli/profiles"
  local profile=""
  local setup=0
  local args=()

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --profile=*) profile="${1#--profile=}"; shift ;;
      --profile)   profile="$2"; shift 2 ;;
      --profiles)
        if [[ -d "$profile_dir" ]]; then
          for f in "$profile_dir"/*.env(N); do echo "${f:t:r}"; done
        else
          echo "No profiles directory: $profile_dir" >&2
        fi
        return 0 ;;
      --setup) setup=1; shift ;;
      *) args+=("$1"); shift ;;
    esac
  done

  if [[ $setup -eq 1 ]]; then
    [[ -z "$profile" ]] && { echo "Usage: sentry-cli --profile=<name> --setup" >&2; return 1; }
    mkdir -p "$profile_dir"
    local pfile="${profile_dir}/${profile}.env"
    if [[ ! -f "$pfile" ]]; then
      cat > "$pfile" <<TMPL
SENTRY_AUTH_TOKEN=
SENTRY_ORG=${profile}
SENTRY_URL=https://${profile}.sentry.io/
TMPL
    fi
    ${EDITOR:-vim} "$pfile"
    return 0
  fi

  if [[ -z "$profile" ]]; then
    "$_sentry_cli" "${args[@]}"
    return $?
  fi

  local pfile="${profile_dir}/${profile}.env"
  [[ ! -f "$pfile" ]] && { echo "Profile not found: $pfile" >&2; return 1; }

  # Run in subshell to avoid polluting current environment
  ( set -a; source "$pfile"; set +a; "$_sentry_cli" "${args[@]}" )
}
```

### Gotcha: The `command -v` Trap

My initial implementation used `command -v sentry-cli` to find the binary path, but zsh's `command -v` also returns functions — causing **infinite recursion** and the dreaded `maximum nested function level reached; increase FUNCNEST?` error.

Use `whence -p` instead, which skips functions and returns only binary paths. This is a zsh built-in.

### Usage

```bash
# Create a profile (opens editor)
sentry-cli --profile=myorg --setup

# Check connection
sentry-cli --profile=myorg info

# List projects
sentry-cli --profile=myorg projects list

# List releases for a specific project
sentry-cli --profile=myorg releases list -p my-project

# List all profiles
sentry-cli --profiles

# Without profile (normal sentry-cli passthrough)
sentry-cli info
```

## Registering as a Claude Code Skill

I also registered this wrapper as a [Claude Code](https://claude.ai/claude-code) skill with a SKILL.md file, enabling the AI agent to naturally use `sentry-cli --profile=xxx` for Sentry operations.

Here's the full skill definition:

```markdown
---
name: sentry-cli
description: Multi-profile wrapper for Sentry CLI. Release management, sourcemap uploads, event sending, etc.
allowed-tools: Bash(sentry-cli:*)
---

# Sentry CLI (multi-profile wrapper)

A zsh wrapper for using Sentry CLI with multiple profiles.

## Important: Check help before operations

If unsure about subcommands or arguments, read the help first.

  sentry-cli --help
  sentry-cli <subcommand> --help

## Profile Management

- Create/edit: sentry-cli --profile=<name> --setup
- List profiles: sentry-cli --profiles
- Run with profile: sentry-cli --profile=<name> <subcommand> [options]
- Run without profile: sentry-cli <subcommand> [options]

## Common Subcommands

- info: Check connection and auth status
- releases: List, create, finalize releases
- sourcemaps: Upload sourcemaps
- send-event: Send test events
- deploys: Record deployments
- issues: List and resolve issues
- projects: List projects
```

Key design decisions:

- **`allowed-tools: Bash(sentry-cli:*)`** grants the agent permission to execute `sentry-cli` commands
- **Instructions to read `--help` before operations** prevent incorrect subcommand arguments
- **Profile management commands and common subcommands** are listed as a quick reference so the agent can use them immediately

## Summary

- sentry-cli has no native multi-profile support
- Sentry MCP Server has OAuth re-authentication friction, heavy context consumption, and weak multi-account support
- A zsh function wrapper enables easy `--profile` switching
- Use `whence -p` instead of `command -v` to avoid infinite recursion
- Register as a Claude Code skill for seamless AI agent integration
