Skip to content

Conversation

@simplesagar
Copy link
Member

Summary

  • Adds new gram install claude-code command for configuring Gram toolsets as MCP servers in Claude Code
  • Supports automatic toolset lookup via --toolset flag (fetches MCP URL and auth config from API)
  • Supports manual configuration via --toolset-url flag with custom headers and environment variables
  • Creates/updates .mcp.json configuration file compatible with Claude Code MCP format
  • Uses mcp-remote npm package for HTTP-based MCP connections

Key Features

Automatic Configuration

gram install claude-code --toolset speakeasy-admin
  • Fetches toolset from Gram API
  • Automatically derives MCP URL, header name, and environment variable from toolset metadata
  • Uses toolset name as the MCP server name

Manual Configuration

gram install claude-code \
  --toolset-url https://chat.speakeasy.com/mcp/speakeasy-admin \
  --header-name Mcp-Speakeasy-Admin-Api-Key \
  --env-var MCP_SPEAKEASY_ADMIN_API_KEY
  • Supports custom MCP URLs
  • Configurable authentication headers
  • Environment variable substitution for secure API key storage

Files Changed

  • cli/internal/app/install.go - New install command implementation
  • cli/internal/claudecode/config.go - Claude Code config file utilities
  • cli/internal/api/toolsets.go - API client for fetching toolset metadata
  • cli/internal/app/app.go - Register install command
  • cli/internal/api/assets.go - Fix: Add missing ServeFunction endpoint

Test Plan

  • Build CLI: mise build:cli
  • Run linters: mise lint:cli (should pass)
  • Test automatic lookup: gram install claude-code --toolset <toolset-slug>
  • Test manual URL: gram install claude-code --toolset-url https://mcp.getgram.ai/org/proj/env --api-key test-key
  • Test env var substitution: gram install claude-code --toolset-url <url> --env-var MY_VAR
  • Verify generated .mcp.json has correct format
  • Test with custom --config-path
  • Verify config works in Claude Code by restarting and checking MCP servers load

🤖 Generated with Claude Code

@simplesagar simplesagar requested a review from a team as a code owner November 5, 2025 22:57
@changeset-bot
Copy link

changeset-bot bot commented Nov 5, 2025

⚠️ No Changeset found

Latest commit: 78d2189

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Nov 5, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
gram Ready Ready Preview Comment Nov 7, 2025 8:26pm
gram-docs-redirect Ready Ready Preview Comment Nov 7, 2025 8:26pm
gram-landing-redirect Ready Ready Preview Comment Nov 7, 2025 8:26pm

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>
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>
Copy link
Contributor

@qstearns qstearns left a 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{
Copy link
Contributor

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

Copy link
Contributor

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

@claude claude bot Nov 6, 2025

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-remote npm package dependency
  • Use Claude Code's native HTTP transport support
  • Align with how claude CLI 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:

  1. Implement the suggested change - Update the fallback config path to use native HTTP transport format?
  2. Just provide analysis - Keep this as a code review comment for discussion?
  3. Something else - Please clarify what action you'd like me to take.

The change would involve modifying:

  • cli/internal/mcp/config.go - Change MCPServerConfig struct and BuildMCPConfig function
  • cli/internal/claudecode/config.go - Update struct to support both formats or just the new format
  • cli/internal/app/install_claude_code.go - Update the config building logic

Let me know how you'd like to proceed!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implement the suggested change

Copy link
Member Author

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

Copy link

@claude claude bot Nov 6, 2025

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-remote npm package
  • ✓ Uses Claude Code's native HTTP transport support
  • ✓ Aligns with how claude CLI 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 - Updated MCPServerConfig struct and BuildMCPConfig function
  • cli/internal/claudecode/config.go - Updated MCPServerConfig struct
  • cli/internal/app/install_claude_code.go - Updated to use HTTP transport fields

Verification

  • ✓ CLI builds successfully
  • go vet passes 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) {
Copy link
Contributor

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",
Copy link
Contributor

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

@claude claude bot Nov 6, 2025

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:

  1. Header format: The "Header-Name:value" format should work - both claude mcp add and mcp-remote expect this format. The colon separates the header name from the value.

  2. Which header to use: The code allows any header name via the --header-name flag. If you need to use Authorization, 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

  1. Current implementation should work for the --header format - both tools expect colon-separated values
  2. Native HTTP transport in config would be better than mcp-remote for the fallback case (as @qstearns noted in another comment)
  3. Multiple headers: If servers need non-auth headers, the current implementation only supports one header. This might need expansion.
  4. Testing needed: Since @qstearns reports this doesn't work for them, there may be an environmental issue or the mcp-remote package 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>
@simplesagar simplesagar changed the title feat: Add gram install claude-code command for MCP server configuration feat: Add gram install command for MCP server configuration & support common clients Nov 6, 2025
…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",
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants