Skip to content

feat(mcp): support custom HTTP headers for non-OAuth MCP servers (#639)#704

Closed
reidliu41 wants to merge 1 commit intonearai:mainfrom
reidliu41:feat/639-mcp-custom-headers
Closed

feat(mcp): support custom HTTP headers for non-OAuth MCP servers (#639)#704
reidliu41 wants to merge 1 commit intonearai:mainfrom
reidliu41:feat/639-mcp-custom-headers

Conversation

@reidliu41
Copy link
Copy Markdown
Contributor

Many MCP servers (Browser-Use, internal services) authenticate via
static HTTP headers rather than OAuth. Previously these servers could
not be connected because IronClaw only supported the OAuth flow.

Add a headers field to McpServerConfig that lets users declare
arbitrary HTTP headers injected into every MCP request. Key design
decisions:

  • has_custom_headers() is independent of requires_auth() so
    existing OAuth semantics are untouched
  • Case-insensitive Authorization conflict detection prevents double
    auth header injection
  • HeaderName/HeaderValue validation at config time rejects CRLF
    injection
  • CLI --header "Name:Value" arg, mcp test header-only passthrough,
    and mcp list --verbose value masking

Usage:
ironclaw mcp add browser-use https://api.browser-use.com
--header "Authorization:Bearer sk-xxx"

Fixes #639

@github-actions github-actions bot added scope: channel/cli TUI / CLI channel scope: tool/mcp MCP client scope: extensions Extension management size: L 200-499 changed lines risk: medium Business logic, config, or moderate-risk modules labels Mar 8, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances IronClaw's compatibility by introducing the capability to configure and utilize custom HTTP headers for MCP server connections. This change broadens the range of supported authentication mechanisms beyond OAuth, enabling seamless interaction with various internal services and browser-use cases that rely on static header-based authentication. The implementation includes robust handling of header parsing, validation, and intelligent conflict resolution for Authorization headers, ensuring both functionality and security.

Highlights

  • Custom HTTP Headers Support: Implemented support for custom HTTP headers in McpServerConfig, allowing connection to MCP servers that use static header authentication instead of OAuth.
  • CLI Integration: The ironclaw mcp add command now accepts a --header "Name:Value" argument to easily configure custom headers for new servers.
  • Authorization Header Conflict Resolution: Added logic to prevent double injection of Authorization headers. If a custom Authorization header is provided, the automatic OAuth Bearer token injection is suppressed.
  • Header Validation and Security: Implemented validation for custom header names and values at configuration time to prevent CRLF injection and ensure header safety.
  • Secure Display of Headers: Header values are now masked in the mcp list --verbose output to prevent sensitive information from being leaked in logs or the terminal.
Changelog
  • src/app.rs
    • Updated client creation logic to consider custom headers when determining if an authenticated client is required.
  • src/cli/mcp.rs
    • Added a new --header argument to the mcp add command for specifying custom HTTP headers.
    • Modified the add_server function to accept and process custom headers.
    • Updated mcp list output to display custom header configuration and mask header values.
    • Adjusted mcp test logic to correctly handle servers configured with custom headers.
  • src/extensions/manager.rs
    • Modified the get_mcp_client method to check for custom headers when deciding whether to create an authenticated client.
  • src/tools/mcp/client.rs
    • Implemented the injection of custom HTTP headers into outgoing requests.
    • Added logic to conditionally inject OAuth Bearer tokens, suppressing them if a custom Authorization header is already present.
    • Introduced new unit and integration tests to verify custom header injection and Authorization header suppression.
  • src/tools/mcp/config.rs
    • Added a headers field to the McpServerConfig struct to store custom HTTP headers.
    • Implemented with_headers, has_custom_headers, and has_custom_auth_header methods for managing and querying custom headers.
    • Added validation within McpServerConfig::validate to ensure custom header names and values are valid and prevent CRLF injection.
    • Updated serialization and deserialization logic to correctly handle the new headers field.
Activity
  • The pull request was opened to address issue Support custom headers for MCP auth #639, which requested support for custom HTTP headers for non-OAuth MCP servers.
  • The author implemented a headers field in McpServerConfig to store custom headers.
  • The CLI was updated to allow users to specify custom headers via a new --header argument.
  • Logic was added to the McpClient to inject these custom headers into requests and to handle potential conflicts with OAuth Authorization headers.
  • Validation was introduced to ensure the safety and correctness of header names and values.
  • The mcp list --verbose command was updated to mask sensitive header values for security.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions bot added the contributor: new First-time contributor label Mar 8, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support for custom HTTP headers for MCP servers. While the implementation includes robust validation and masking of sensitive header values in CLI output, a critical security concern was identified regarding the plaintext storage of these headers in configuration files and the database. It is strongly recommended to encrypt these headers using the existing SecretsStore or ensure restricted filesystem permissions for configuration files, aligning with best practices for handling sensitive tokens. Additionally, a minor suggestion is provided to improve code readability and maintainability in the header processing logic.

Comment thread src/tools/mcp/config.rs
/// Used for MCP servers that require non-OAuth authentication
/// (e.g., `X-API-Key`, `Authorization: Bearer <static-token>`).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub headers: Option<HashMap<String, String>>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security-medium medium

Insecure Storage of Sensitive Custom Headers

Severity: Medium
Sub-category: Insecure Data Handling

Description:
Custom HTTP headers provided via the --header flag are stored in plaintext in the mcp-servers.json configuration file and the database settings table. These headers are intended for authentication (e.g., Authorization: Bearer <token>) and frequently contain sensitive API keys or session tokens. Storing them in plaintext increases the risk of credential theft if the configuration file or database is accessed by unauthorized users or compromised. While the application correctly masks these values when displaying them in the terminal, the underlying storage remains unencrypted.

Impact:
An attacker with local access to the user's filesystem or access to the database can retrieve sensitive API keys and tokens, potentially gaining unauthorized access to the associated MCP servers and services.

Remediation:
Sensitive headers should be stored in the encrypted SecretsStore instead of the plaintext configuration. Alternatively, the application should ensure that the mcp-servers.json file is created with restricted file permissions (e.g., 0600 on Unix-like systems) to prevent other users on the same machine from reading its contents.

References
  1. This rule highlights the necessity of supporting custom headers for service-specific requirements, which often involve sensitive authentication tokens. Consequently, the secure storage and handling of these headers are paramount to prevent credential leakage.

Comment thread src/tools/mcp/client.rs
Comment on lines +182 to +195
let has_custom_auth = if let Some(ref config) = self.server_config
&& config.has_custom_headers()
{
let has_auth = config.has_custom_auth_header();
if let Some(ref headers) = config.headers {
for (name, value) in headers {
// Headers were validated at config time (CRLF-safe)
req_builder = req_builder.header(name.as_str(), value.as_str());
}
}
has_auth
} else {
false
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This block for adding custom headers and checking for a custom Authorization header is a bit complex, as it mixes a side effect (modifying req_builder) inside an expression. This can be refactored for better readability and maintainability by separating the action of adding headers from the check for the authorization header.

            let has_custom_auth = if let Some(config) = self.server_config.as_ref() {
                if let Some(headers) = &config.headers {
                    for (name, value) in headers {
                        // Headers were validated at config time (CRLF-safe)
                        req_builder = req_builder.header(name.as_str(), value.as_str());
                    }
                    config.has_custom_auth_header()
                } else {
                    false
                }
            } else {
                false
            };

Copy link
Copy Markdown
Collaborator

@zmanian zmanian left a comment

Choose a reason for hiding this comment

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

The feature is well-implemented -- CRLF injection prevention, case-insensitive Authorization conflict detection, header value masking, and wire-level integration tests are all solid work.

However, this PR has heavy conflicts with #721 (MCP transport abstraction by ilblackdragon). Both PRs touch the same 5 files and add custom headers support with different designs:

  • #704: Option<HashMap<String, String>>
  • #721: HashMap<String, String> with #[serde(default, skip_serializing_if)]

Recommendation: Merge #721 first, then port #704's unique contributions as a follow-up PR:

  1. CRLF header validation -- #721 doesn't validate header names/values at config time
  2. has_custom_auth_header() -- #721's build_request_headers() unconditionally overwrites custom Authorization headers with OAuth tokens. This is a bug that #704's approach fixes correctly.
  3. CLI --header "Name:Value" argument and parsing
  4. Header value masking in mcp list --verbose
  5. Wire-level echo server tests

The Authorization conflict bug in #721 should be filed separately -- your has_custom_auth_header() logic is the correct approach.

Also: The masking code in src/cli/mcp.rs uses byte-index slicing (&value[..2]), which can panic on multi-byte characters per the project's UTF-8 safety rules. Use char_indices() or is_char_boundary().

@reidliu41
Copy link
Copy Markdown
Contributor Author

Closing in favor of a focused follow-up PR.

#721 (MCP transport abstraction) has been merged and already covers custom headers support
(--header CLI argument, header value masking in mcp list --verbose). Rebasing #704 would
require resolving conflicts across all 5 changed files due to #721's architectural refactor.

The unique contributions from #704 that #721 doesn't cover will be ported in a new PR based on
current main:

Now let me implement the new PR #752.

@reidliu41 reidliu41 closed this Mar 9, 2026
reidliu41 added a commit to reidliu41/ironclaw that referenced this pull request Mar 9, 2026
zmanian pushed a commit that referenced this pull request Mar 11, 2026
…#704 (#752)

* fix(mcp): header safety validation and Authorization conflict bug from #704

* fix(mcp): enforce RFC 9110 header validation on all config load paths

  Replace hand-written CRLF checks with reqwest::header::HeaderName::from_bytes()
  and HeaderValue::from_str(), catching spaces, colons, null bytes, and all
  non-token characters that the previous validation missed.

  Add validation to load_mcp_servers_from() and load_mcp_servers_from_db() so
  corrupted configs from disk or DB are rejected at load time instead of silently
  flowing through to McpClient. Improve app.rs error handling to distinguish
  "no config" from "corrupted config" (including malformed JSON).

  Also fix build_request_headers() to check self.custom_headers directly instead
  of indirectly via server_config, and clarify the wire test comment about
  HeaderMap::insert replacement semantics.

* fix ci issue
@ironclaw-ci ironclaw-ci bot mentioned this pull request Mar 12, 2026
bkutasi pushed a commit to bkutasi/ironclaw that referenced this pull request Mar 28, 2026
…nearai#704 (nearai#752)

* fix(mcp): header safety validation and Authorization conflict bug from nearai#704

* fix(mcp): enforce RFC 9110 header validation on all config load paths

  Replace hand-written CRLF checks with reqwest::header::HeaderName::from_bytes()
  and HeaderValue::from_str(), catching spaces, colons, null bytes, and all
  non-token characters that the previous validation missed.

  Add validation to load_mcp_servers_from() and load_mcp_servers_from_db() so
  corrupted configs from disk or DB are rejected at load time instead of silently
  flowing through to McpClient. Improve app.rs error handling to distinguish
  "no config" from "corrupted config" (including malformed JSON).

  Also fix build_request_headers() to check self.custom_headers directly instead
  of indirectly via server_config, and clarify the wire test comment about
  HeaderMap::insert replacement semantics.

* fix ci issue
drchirag1991 pushed a commit to drchirag1991/ironclaw that referenced this pull request Apr 8, 2026
…nearai#704 (nearai#752)

* fix(mcp): header safety validation and Authorization conflict bug from nearai#704

* fix(mcp): enforce RFC 9110 header validation on all config load paths

  Replace hand-written CRLF checks with reqwest::header::HeaderName::from_bytes()
  and HeaderValue::from_str(), catching spaces, colons, null bytes, and all
  non-token characters that the previous validation missed.

  Add validation to load_mcp_servers_from() and load_mcp_servers_from_db() so
  corrupted configs from disk or DB are rejected at load time instead of silently
  flowing through to McpClient. Improve app.rs error handling to distinguish
  "no config" from "corrupted config" (including malformed JSON).

  Also fix build_request_headers() to check self.custom_headers directly instead
  of indirectly via server_config, and clarify the wire test comment about
  HeaderMap::insert replacement semantics.

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

Labels

contributor: new First-time contributor risk: medium Business logic, config, or moderate-risk modules scope: channel/cli TUI / CLI channel scope: extensions Extension management scope: tool/mcp MCP client size: L 200-499 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support custom headers for MCP auth

2 participants