Skip to content

Commit b8ed917

Browse files
simplesagarclaude
andauthored
feat: add --scope flag to gram install command (#819)
## Summary - Add `--scope` flag to `gram install` command to control MCP server installation location - Support both `user` (default) and `project` scope levels - Apply scope across Claude Code and Gemini CLI installations ## Changes - **New flag**: `--scope` with values `user` (default) or `project` - `user`: Installs to `~/.claude/settings.local.json` (user-level config) - `project`: Installs to `.mcp.json` in current directory (project-level config) - **Claude Code**: Updated to pass scope to both native `claude` CLI and fallback config file writer - Maps `project` → `local` for Claude CLI compatibility - **Gemini CLI**: Updated to pass scope parameter to `gemini mcp add` command - **Default behavior**: Changed to `user` scope for better portability across projects ## Usage Examples ```bash # Install to user-level config (default) gram install claude-code --toolset my-toolset # Install to project-level config gram install claude-code --toolset my-toolset --scope project # Install using Gemini CLI gram install gemini-cli --toolset my-toolset --scope user ``` ## Test plan - [x] Build succeeds: `mise build:cli` - [x] Linting passes: `mise lint:cli` - [x] Manual testing with Claude Code installation (both scopes) - [x] Manual testing with Gemini CLI installation (both scopes) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <[email protected]>
1 parent 8f1b8f8 commit b8ed917

File tree

6 files changed

+58
-13
lines changed

6 files changed

+58
-13
lines changed

.changeset/four-shirts-cheer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"cli": patch
3+
---
4+
5+
feat: add --scope flag to gram install command to determine whether the mcp config is added to the user, project or local config locations.

cli/internal/app/install.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ var installFlags = []cli.Flag{
3636
Name: "env-var",
3737
Usage: "Environment variable name to use for API key substitution (e.g., MCP_API_KEY). If provided, uses ${VAR} syntax instead of hardcoding the key",
3838
},
39+
&cli.StringFlag{
40+
Name: "scope",
41+
Usage: "Configuration scope: 'project' for .mcp.json in current directory, 'user' for ~/.claude/settings.local.json (defaults to user)",
42+
Value: "user",
43+
},
3944
}
4045

4146
func newInstallCommand() *cli.Command {

cli/internal/app/install_claude_code.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ func doInstallClaudeCode(c *cli.Context) error {
3030
}
3131

3232
useEnvVar := info.EnvVarName != ""
33+
scope := c.String("scope")
34+
3335
if useEnvVar {
3436
logger.InfoContext(ctx, "using environment variable substitution",
3537
slog.String("var", info.EnvVarName),
@@ -38,9 +40,10 @@ func doInstallClaudeCode(c *cli.Context) error {
3840

3941
// Try to use native claude CLI with HTTP transport first
4042
if mcp.IsClaudeCLIAvailable() {
41-
logger.InfoContext(ctx, "using claude CLI with native HTTP transport")
43+
logger.InfoContext(ctx, "using claude CLI with native HTTP transport",
44+
slog.String("scope", scope))
4245

43-
if err := mcp.InstallViaClaudeCLI(info, useEnvVar); err != nil {
46+
if err := mcp.InstallViaClaudeCLI(info, useEnvVar, scope); err != nil {
4447
logger.WarnContext(ctx, "claude CLI installation failed, falling back to config file",
4548
slog.String("error", err.Error()))
4649
} else {
@@ -69,10 +72,26 @@ func doInstallClaudeCode(c *cli.Context) error {
6972
if err != nil {
7073
return fmt.Errorf("failed to get config locations: %w", err)
7174
}
72-
configPath := locations[0].Path
75+
76+
// Determine config path based on scope flag (already declared above)
77+
var configPath string
78+
var configDesc string
79+
80+
switch scope {
81+
case "project":
82+
configPath = locations[0].Path
83+
configDesc = locations[0].Description
84+
case "user":
85+
configPath = locations[1].Path
86+
configDesc = locations[1].Description
87+
default:
88+
return fmt.Errorf("invalid scope '%s': must be 'project' or 'user'", scope)
89+
}
90+
7391
logger.InfoContext(ctx, "using config location",
7492
slog.String("path", configPath),
75-
slog.String("type", locations[0].Description))
93+
slog.String("type", configDesc),
94+
slog.String("scope", scope))
7695

7796
config, err := claudecode.ReadConfig(configPath)
7897
if err != nil {

cli/internal/app/install_gemini_cli.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,19 @@ func doInstallGeminiCLI(c *cli.Context) error {
2828
}
2929

3030
useEnvVar := info.EnvVarName != ""
31+
scope := c.String("scope")
32+
3133
if useEnvVar {
3234
logger.InfoContext(ctx, "using environment variable substitution",
3335
slog.String("var", info.EnvVarName),
3436
slog.String("header", info.HeaderName))
3537
}
3638

37-
// Execute: gemini mcp add --transport http "name" "url" --header "Header:${VAR}"
38-
logger.InfoContext(ctx, "installing via gemini CLI with native HTTP transport")
39+
// Execute: gemini mcp add --transport http --scope <scope> "name" "url" --header "Header:${VAR}"
40+
logger.InfoContext(ctx, "installing via gemini CLI with native HTTP transport",
41+
slog.String("scope", scope))
3942

40-
if err := mcp.InstallViaGeminiCLI(info, useEnvVar); err != nil {
43+
if err := mcp.InstallViaGeminiCLI(info, useEnvVar, scope); err != nil {
4144
return fmt.Errorf("failed to install via gemini CLI: %w", err)
4245
}
4346

cli/internal/mcp/claude_cli.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ func IsClaudeCLIAvailable() bool {
1212
}
1313

1414
// InstallViaClaudeCLI installs an MCP server using the native claude CLI
15-
// Uses: claude mcp add --transport http "name" "url" --header "Header:${VAR}"
15+
// Uses: claude mcp add --transport http --scope <scope> "name" "url" --header "Header:${VAR}"
16+
// scope: "project" (maps to claude CLI's "local") or "user"
1617
// Returns an error if the claude CLI is not available
17-
func InstallViaClaudeCLI(info *ToolsetInfo, useEnvVar bool) error {
18+
func InstallViaClaudeCLI(info *ToolsetInfo, useEnvVar bool, scope string) error {
1819
var headerValue string
1920

2021
if useEnvVar {
@@ -25,11 +26,20 @@ func InstallViaClaudeCLI(info *ToolsetInfo, useEnvVar bool) error {
2526
headerValue = fmt.Sprintf("%s:%s", info.HeaderName, info.APIKey)
2627
}
2728

28-
// Build command: claude mcp add --transport http "name" "url" --header "Header:value"
29+
// Map our scope terminology to claude CLI's scope terminology
30+
// Our "project" -> Claude CLI's "local" (.mcp.json in current directory)
31+
// Our "user" -> Claude CLI's "user" (~/.claude/settings.local.json)
32+
claudeScope := scope
33+
if scope == "project" {
34+
claudeScope = "local"
35+
}
36+
37+
// Build command: claude mcp add --transport http --scope <scope> "name" "url" --header "Header:value"
2938
args := []string{
3039
"mcp",
3140
"add",
3241
"--transport", "http",
42+
"--scope", claudeScope,
3343
info.Name,
3444
info.URL,
3545
"--header", headerValue,

cli/internal/mcp/gemini_cli.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ func IsGeminiCLIAvailable() bool {
1212
}
1313

1414
// InstallViaGeminiCLI installs an MCP server using the native gemini CLI
15-
// Uses: gemini mcp add --transport http "name" "url" --header "Header:${VAR}"
16-
func InstallViaGeminiCLI(info *ToolsetInfo, useEnvVar bool) error {
15+
// Uses: gemini mcp add --transport http --scope <scope> "name" "url" --header "Header:${VAR}"
16+
// scope: "project" or "user" (will be passed as-is if gemini CLI supports it)
17+
func InstallViaGeminiCLI(info *ToolsetInfo, useEnvVar bool, scope string) error {
1718
var headerValue string
1819

1920
if useEnvVar {
@@ -24,11 +25,13 @@ func InstallViaGeminiCLI(info *ToolsetInfo, useEnvVar bool) error {
2425
headerValue = fmt.Sprintf("%s:%s", info.HeaderName, info.APIKey)
2526
}
2627

27-
// Build command: gemini mcp add --transport http "name" "url" --header "Header:value"
28+
// Build command: gemini mcp add --transport http --scope <scope> "name" "url" --header "Header:value"
29+
// Note: scope support depends on gemini CLI version
2830
args := []string{
2931
"mcp",
3032
"add",
3133
"--transport", "http",
34+
"--scope", scope,
3235
info.Name,
3336
info.URL,
3437
"--header", headerValue,

0 commit comments

Comments
 (0)