-
Notifications
You must be signed in to change notification settings - Fork 13
feat: Add gram install command for MCP server configuration & support common clients #776
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Implements a new CLI command that configures Gram toolsets as MCP servers in Claude Code. Supports both automatic toolset lookup via API and manual configuration with custom URLs and headers. Also fixes missing ServeFunction endpoint in cli/internal/api/assets.go that was added to the generated code in PR #769 but not propagated to the handwritten client wrapper. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
fbc1d2f to
6541476
Compare
Refactors the install command to use a shared architecture that can support multiple MCP clients. Adds Cursor installation support via deep links. Changes: - Extract shared toolset resolution logic to cli/internal/mcp/resolver.go - Extract shared MCP config building to cli/internal/mcp/config.go - Add cli/internal/mcp/browser.go for opening deep links - Refactor claude-code to use shared components - Add gram install cursor command with deep link support The new architecture makes it easy to add support for additional clients (Windsurf, Claude Desktop, Gemini CLI, Codex CLI) in future PRs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Moves claude-code implementation from install.go to install_claude_code.go to match the pattern established by install_cursor.go. Now install.go only contains command registration. This makes the structure consistent: - install.go: Command registration only - install_claude_code.go: Claude Code implementation - install_cursor.go: Cursor implementation Makes it easier to add future clients (windsurf, claude-desktop, etc.) following the same pattern. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Updates Claude Code installation to use the native `claude mcp add --transport http` command, which uses Claude Code's built-in HTTP transport instead of requiring npx/mcp-remote. Falls back to writing .mcp.json config file if claude CLI is not available. Benefits: - No npm dependency when using claude CLI - Native HTTP transport (matches hosted install page) - Direct installation via claude command - Automatic fallback for users without claude CLI The command now: 1. Checks if claude CLI is available in PATH 2. If yes: runs `claude mcp add --transport http` with appropriate headers 3. If no: falls back to writing .mcp.json with mcp-remote (previous behavior) This matches the approach shown on Gram's hosted MCP install pages. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
qstearns
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple comments but good add. I think a big missing piece here seems like we're missing sending non-authentication headers for servers that require other configuration.
This seems pretty sweet - will def be using.
| envConfig = map[string]string{} | ||
| } | ||
|
|
||
| return MCPServerConfig{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I could be mistaken, but I'm almost certain you can structure your object like:
{
"serverName": {
"type": "http",
"url": "..."
}
}and use the http transport natively
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what claude code does on its own and more and more clients (including cursor) are supporting this formulation so I think it might be worth going this route
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Claude finished @simplesagar's task —— View job
Analyzing @qstearns's suggestion about native HTTP transport
Tasks
- Read the relevant code files to understand the current implementation
- Analyze @qstearns's suggestion about native HTTP transport
- Clarify what action is requested
Current Implementation
The current code has two pathways for installing MCP servers in Claude Code:
1. Native claude CLI (preferred) - cli/internal/mcp/claude_cli.go:28-36
claude mcp add --transport http <name> <url> --header "Header:value"2. Fallback via config file - cli/internal/mcp/config.go:32-42
{
"mcpServers": {
"name": {
"command": "npx",
"args": ["-y", "mcp-remote", "url", "--header", "Header:value"]
}
}
}@qstearns's Suggestion
The review comment suggests that the fallback path should use native HTTP transport format instead of mcp-remote:
{
"mcpServers": {
"serverName": {
"type": "http",
"url": "https://...",
"headers": {
"Gram-Apikey": "value"
}
}
}
}This would:
- Remove the
mcp-remotenpm package dependency - Use Claude Code's native HTTP transport support
- Align with how
claudeCLI configures servers - Match what Cursor and other clients support
Question for @simplesagar
You tagged me but didn't specify what you'd like me to do. Would you like me to:
- Implement the suggested change - Update the fallback config path to use native HTTP transport format?
- Just provide analysis - Keep this as a code review comment for discussion?
- Something else - Please clarify what action you'd like me to take.
The change would involve modifying:
cli/internal/mcp/config.go- ChangeMCPServerConfigstruct andBuildMCPConfigfunctioncli/internal/claudecode/config.go- Update struct to support both formats or just the new formatcli/internal/app/install_claude_code.go- Update the config building logic
Let me know how you'd like to proceed!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implement the suggested change
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@claude Implement the suggested change
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Claude finished @simplesagar's task —— View job
Tasks
- Read current MCP config implementation
- Read Claude Code config implementation
- Understand current structure and dependencies
- Update config structs to support native HTTP transport format
- Update BuildMCPConfig to generate native HTTP config
- Build CLI to verify changes
- Run linter to ensure code quality
- Commit and push changes
Summary
Successfully implemented @qstearns's suggestion to use native HTTP transport instead of the mcp-remote npm package for Claude Code MCP server configuration.
Changes Made
Updated the MCP configuration to use Claude Code's built-in HTTP transport:
Before (using mcp-remote):
{
"mcpServers": {
"name": {
"command": "npx",
"args": ["-y", "mcp-remote", "url", "--header", "Header:value"]
}
}
}After (native HTTP transport):
{
"mcpServers": {
"name": {
"type": "http",
"url": "https://...",
"headers": {
"Header-Name": "value"
}
}
}
}Benefits
- ✓ Removes dependency on
mcp-remotenpm package - ✓ Uses Claude Code's native HTTP transport support
- ✓ Aligns with how
claudeCLI configures servers - ✓ Matches format supported by Cursor and other MCP clients
- ✓ Maintains backward compatibility with other clients (Claude Desktop, etc.)
Files Modified
cli/internal/mcp/config.go- UpdatedMCPServerConfigstruct andBuildMCPConfigfunctioncli/internal/claudecode/config.go- UpdatedMCPServerConfigstructcli/internal/app/install_claude_code.go- Updated to use HTTP transport fields
Verification
- ✓ CLI builds successfully
- ✓
go vetpasses with no issues - ✓ Changes committed and pushed to PR branch
|
| } | ||
|
|
||
| // AddOrUpdateServer adds or updates an MCP server in the configuration | ||
| func (c *MCPConfig) AddOrUpdateServer(name string, serverConfig MCPServerConfig) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Product decision but claude (and most clients) seem to error on name collision rather than updating in place. I think we should probably prefer this behavior on behalf of all gram users with bad naming hygiene
| &cli.StringFlag{ | ||
| Name: "header-name", | ||
| Usage: "The HTTP header name for the API key (defaults to Gram-Apikey)", | ||
| Value: "Gram-Apikey", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sending the header like this doesn't work for me and I'm not exactly sure as to why. I always send the header as Authorization, but I know there's a way to make another incantation work. I can just never seem to figure it out. Just wanna be absolutely sure that this works for you though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Claude finished @simplesagar's task —— View job
Analysis of Header Configuration
Tasks
- Read the install_claude_code.go file to understand the header implementation
- Check related MCP configuration files
- Analyze the header sending approach
- Provide technical feedback
Header Configuration Analysis
The implementation at line 40 in cli/internal/app/install_claude_code.go defines the default header name as "Gram-Apikey". This is the flag definition, not the actual header sending mechanism.
How Headers Are Actually Sent
The header sending happens in two different ways depending on whether claude CLI is available:
1. Native HTTP Transport (via claude CLI) - cli/internal/mcp/claude_cli.go:
// Lines 21-26
if useEnvVar {
headerValue = fmt.Sprintf("%s:${%s}", info.HeaderName, info.EnvVarName)
} else {
headerValue = fmt.Sprintf("%s:%s", info.HeaderName, info.APIKey)
}
// Lines 28-36
args := []string{
"mcp",
"add",
"--transport", "http",
info.Name,
info.URL,
"--header", headerValue, // Format: "Header-Name:value"
}2. Fallback via mcp-remote package - cli/internal/mcp/config.go:
// Lines 32-40
return MCPServerConfig{
Command: "npx",
Args: []string{
"-y",
"mcp-remote",
info.URL,
"--header",
headerValue, // Format: "Header-Name:value"
},
Env: envConfig,
}Both approaches use the same format: "Header-Name:value" (with a colon separator).
Addressing @qstearns's Concern
@qstearns mentioned this approach doesn't work for them and they always use Authorization. There are two separate issues here:
-
Header format: The
"Header-Name:value"format should work - bothclaude mcp addandmcp-remoteexpect this format. The colon separates the header name from the value. -
Which header to use: The code allows any header name via the
--header-nameflag. If you need to useAuthorization, you can:gram install claude-code --toolset foo --header-name Authorization
Potential Issues
The current implementation may have a problem if the MCP URL itself requires headers beyond authentication. Looking at the review comment on cli/internal/mcp/config.go:32, @qstearns noted that clients support native HTTP transport, which this PR doesn't fully utilize in the fallback case.
The fallback still uses mcp-remote (an npm package), which adds an extra dependency and layer. The native approach would configure the MCP server to use HTTP transport directly in the config file:
{
"mcpServers": {
"serverName": {
"type": "http",
"url": "https://mcp.getgram.ai/...",
"headers": {
"Gram-Apikey": "key-value"
}
}
}
}Recommendations
- Current implementation should work for the
--headerformat - both tools expect colon-separated values - Native HTTP transport in config would be better than
mcp-remotefor the fallback case (as @qstearns noted in another comment) - Multiple headers: If servers need non-auth headers, the current implementation only supports one header. This might need expansion.
- Testing needed: Since @qstearns reports this doesn't work for them, there may be an environmental issue or the
mcp-remotepackage might have requirements not being met
@qstearns - Can you share what error you're seeing when you try this? That would help diagnose whether it's the header format, the mcp-remote package, or something else.
Adds support for two more MCP clients: 1. Claude Desktop (.dxt file generation) - Generates .dxt manifest files that can be double-clicked to install - Supports user_config for secure environment variable prompts - Downloads to ~/Downloads by default (configurable with --output-dir) 2. Gemini CLI (native command execution) - Uses native `gemini mcp add --transport http` command - Requires gemini CLI to be installed - Same HTTP transport approach as Claude Code Both clients support: - Automatic toolset lookup with --toolset - Manual configuration with --toolset-url - Environment variable substitution (Claude Desktop prompts at install time) - Direct API key embedding The PR now supports 4 clients: - Claude Code (native HTTP or config file) - Claude Desktop (.dxt file) - Cursor (deep link) - Gemini CLI (native HTTP) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Implements team feedback:
1. Renamed --toolset-url to --mcp-url for better clarity
- Makes more sense since we're configuring MCP servers
- Updated across all 4 client implementations
2. Added support for reading from local environment variables
- If --env-var MCP_API_KEY is provided AND the variable is already set locally, uses the actual value
- If variable is NOT set, uses ${VAR} substitution (previous behavior)
- Smart detection: "using API key from local environment variable" vs "will use substitution"
Example workflow:
```bash
# Set your API key locally
export MCP_SPEAKEASY_ADMIN_API_KEY='my-secret-key'
# Install with just the env var name - it reads the local value
gram install claude-code \
--mcp-url https://chat.speakeasy.com/mcp/speakeasy-admin \
--env-var MCP_SPEAKEASY_ADMIN_API_KEY
# Config gets the actual value (not substitution) since it's set locally
```
This makes the UX smoother - users who already have env vars configured don't need to handle substitution.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
…Code
Replace mcp-remote npm package with native HTTP transport configuration
for Claude Code MCP servers. This removes an external dependency and
aligns with how claude CLI configures servers natively.
Changes:
- Updated MCPServerConfig to support both command-based (legacy) and HTTP transport (native)
- Modified BuildMCPConfig to generate native HTTP transport format
- Updated install_claude_code to use new HTTP transport fields
Config format changed from:
{
"command": "npx",
"args": ["-y", "mcp-remote", "url", "--header", "Header:value"]
}
To:
{
"type": "http",
"url": "https://...",
"headers": {"Header-Name": "value"}
}
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Sagar Batchu <simplesagar@users.noreply.github.com>
| &cli.StringFlag{ | ||
| Name: "header-name", | ||
| Usage: "The HTTP header name for the API key (defaults to Gram-Apikey)", | ||
| Value: "Gram-Apikey", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is for Gram API Keys accessing servers it should be Authorization bearer
Summary
gram install claude-codecommand for configuring Gram toolsets as MCP servers in Claude Code--toolsetflag (fetches MCP URL and auth config from API)--toolset-urlflag with custom headers and environment variables.mcp.jsonconfiguration file compatible with Claude Code MCP formatmcp-remotenpm package for HTTP-based MCP connectionsKey Features
Automatic Configuration
Manual Configuration
Files Changed
cli/internal/app/install.go- New install command implementationcli/internal/claudecode/config.go- Claude Code config file utilitiescli/internal/api/toolsets.go- API client for fetching toolset metadatacli/internal/app/app.go- Register install commandcli/internal/api/assets.go- Fix: Add missing ServeFunction endpointTest Plan
mise build:climise lint:cli(should pass)gram install claude-code --toolset <toolset-slug>gram install claude-code --toolset-url https://mcp.getgram.ai/org/proj/env --api-key test-keygram install claude-code --toolset-url <url> --env-var MY_VAR.mcp.jsonhas correct format--config-path🤖 Generated with Claude Code