Skip to content

fix(mcp): keep a credential typed into the MCP component's Headers box - #15761

Merged
erichare merged 6 commits into
release-1.13.0from
fix/mcp-tools-header-credentials
Oct 15, 2026
Merged

erichare merged 6 commits into
release-1.13.0from
fix/mcp-tools-header-credentials

Conversation

@Jkavia

@Jkavia Jkavia commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

QA found that a credential typed into the MCP Tools component's Headers box is silently dropped at deploy.

That box overrides the headers set on the server, so it is a reasonable place to put an Authorization header. Packaging removed the value, which is right, but nothing declared a variable to put it back. The manifest named nothing, the deploy check said ready, and the deployed flow called the server with no header at all. You found out when someone else's API returned a 401.

Fix

  • A credential in that box is now handled the same way as one in the server's own config: saving the flow moves it into a global variable and leaves the generated name behind, packaging keeps that name and declares it, and the deploy refuses by name until the target has it.
  • Both storage shapes. The input is DictInput(is_list=True), so the canvas stores it as a mapping or as {"key", "value"} rows and the component reads either one.
  • The box's variables are scoped apart from the server config's, so a header set in both places keeps two values instead of one overwriting the other.

Still removed, deliberately

A literal that was never rewritten, a name that is not the one this server and field generate, every value when the reader has no variable manifest (a public share), and anything the ordinary rules already catch, such as a credential inside a URL under an innocent header name.

Scope

MCP Tools only. The same nulling affects API Request, URL, Cloudflare and Confluent Kafka Producer, and fixing those needs a decision this change cannot make: an MCP server has a name to key the variable on and they do not. Tracked separately.

Testing

  • 14 new tests covering both storage shapes, the round trip through save and packaging, and each case that must stay nulled.
  • 1317 passing across unit/api, unit/utils and unit/services/deployment_artifacts.
  • Not a regression. This path has behaved this way independently of recent deployment work.

Summary by CodeRabbit

  • Bug Fixes
    • Secret values in MCP server headers are now securely extracted and stored as variables, including when a server is selected by name without inline configuration.
    • Header secrets are associated with the selected server, while non-secret headers remain unchanged.
    • Packaged flows preserve valid generated variable references and safely clear references that are unavailable or unrecognized.

The MCP Tools component has a Headers input beside its server field, and its own
help text says those headers override the ones configured on the server. So it
is a reasonable place to put an Authorization header, and people do.

Packaging nulled it. The value is secret-named, so the scrubber removed it --
correctly, because a package is a file people store and share -- but nothing
declared a variable to put it back. The manifest named nothing, the deploy check
reported ready, and the deployed flow called the server with no header at all.
The failure surfaced as a 401 from someone else's service, with nothing anywhere
connecting it to a credential the deploy had removed.

Handled the same way as a credential in the server's own config, because the box
is the same shape as a config's `headers` map and the existing scrubber already
knows how to name one. Saving the flow moves the literal into a global variable
and leaves the generated name behind; packaging keeps that name and declares it;
the deploy refuses until the target has it.

Both storage shapes. The input is `DictInput(is_list=True)`, so the canvas stores
it as a mapping or as `{"key", "value"}` rows and the component reads either at
run time. Handling one and not the other would have missed half the flows.

Scoped to this component. The same nulling affects API Request, URL, Cloudflare
and Confluent Kafka Producer, and fixing those needs a decision this change
cannot make: an MCP server has a name to key the variable on and they do not.
Tracked separately.

What is deliberately still nulled: a literal that was never rewritten, a name
that is not the one this server and field generate, every value when the reader
has no variable manifest (a public share), and anything the ordinary rules
catch -- a credential inside a URL under an innocent header name, or one nested
deeper in a value.

Four defects in earlier drafts of this change were found by open-code-review and
each reproduced before being fixed:

- Skipping the generic pass for the whole field switched off scrubbing it was
  doing. A URL carrying credentials under a non-secret header name survived.
  References are now lifted out, the ordinary scrub runs, and they go back --
  the shape `_strip_table_rows_in_place` already uses.
- The row form of the box was skipped entirely at save time.
- The box and the server config generated the same variable name, so whichever
  was written last won. In practice the config, which silently discarded the
  override -- the one thing the box exists to do. Both sides now derive from
  `mcp_header_scope`.
- Save time rewrites every header outside `NON_SECRET_HEADERS`; packaging only
  kept the ones `_is_secret_name` matched, so `X-Tenant` got a generated name
  that nothing declared and nothing would resolve.

Found by QA against MCP Tools. Not a regression: this path has behaved this way
independently of recent deployment work.
@github-actions github-actions Bot added the bug Something isn't working label Oct 14, 2026
@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: langflow-ai/langflow/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e70e8131-09d5-473e-95b1-ba8a73230383

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: langflow-ai/langflow/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 187c91e5-0d3a-4eeb-889a-25fbe886fd10
📥 Commits

Reviewing files that changed from the base of the PR and between e5e21ef and 795e726.

📒 Files selected for processing: 3
  • src/backend/base/langflow/api/utils/mcp/flow_secrets.py
  • src/backend/base/langflow/utils/flow_secrets.py
  • src/backend/tests/unit/api/utils/mcp/test_flow_secrets.py

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Walkthrough

The change extracts credentials from the MCP component’s separate Headers input into server-scoped variables. Packaging preserves recognized generated references and scrubs other header values for mapping and key/value-row formats.

Changes

MCP Header Secret Handling

Layer / File(s) Summary
Header reference scrubbing
src/backend/base/langflow/utils/flow_secrets.py, src/backend/tests/unit/api/utils/mcp/test_flow_secrets.py
Header handling supports mapping and key/value-row values. Packaging preserves recognized generated references and scrubs other values. Tests cover invalid or unavailable references and custom headers.
Component Headers extraction
src/backend/base/langflow/api/utils/mcp/flow_secrets.py, src/backend/tests/unit/api/utils/mcp/test_flow_secrets.py
MCP field traversal now supplies each field’s template. Extraction scrubs the component’s Headers input even when its server has no dictionary config, and scopes credentials by server name. Tests cover name-selected servers and distinct variables for component headers and server config.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant MCPFlowExtraction
  participant MCPServerFieldIterator
  participant ComponentHeadersScrubber
  participant SecretVariables
  MCPFlowExtraction->>MCPServerFieldIterator: iterate server fields and templates
  MCPServerFieldIterator-->>MCPFlowExtraction: return field and template
  MCPFlowExtraction->>ComponentHeadersScrubber: scrub Headers using server name
  ComponentHeadersScrubber->>SecretVariables: add scoped header credentials
Loading

Suggested reviewers: erichare

Merge Risk: ⚪ Minimal · up to 795e7

No identified issue remains that should block merging after normal checks.

Pre-merge checks: Passed 8 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 79.17% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 24 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 8 passed
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Test Coverage For New Implementations Passed The PR adds regression coverage in the backend-named file src/backend/tests/unit/api/utils/mcp/test_flow_secrets.py. The tests cover mapping and row header storage, save-time extraction, packaging r…
Test Quality And Coverage Passed The added pytest coverage tests the main MCP Headers behavior. It covers mapping and key/value-row storage, save-time variable extraction, packaging reference retention, unknown and missing-manifest s…
Test File Naming And Structure Passed The PR adds tests in src/backend/tests/unit/api/utils/mcp/test_flow_secrets.py, which matches the backend test_*.py pattern. The file uses pytest imports, pytest.mark.parametrize, discoverable `…
Excessive Mock Usage Warning Passed The pull request adds 14 tests without adding mocks, patches, or mock objects. The new tests exercise real secret extraction and packaging behavior with plain dictionaries and small test doubles. Exis…
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: preserving credentials entered in the MCP component's Headers box.
✨ Finishing Touches: 💡 2
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

✅ Test Coverage Advisor

No source changes detected without accompanying tests. Thanks for keeping coverage up! 🎉

Advisory check only — never blocks merge.

@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Oct 14, 2026
@codecov

codecov Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.18605% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.93%. Comparing base (27ff937) to head (cc7f9e4).
⚠️ Report is 15 commits behind head on release-1.13.0.

Files with missing lines Patch % Lines
...ackend/base/langflow/api/utils/mcp/flow_secrets.py 97.05% 1 Missing ⚠️
...ckend/base/langflow/api/v1/flow_conflict_routes.py 0.00% 1 Missing ⚠️
src/backend/base/langflow/api/v1/flows.py 80.00% 1 Missing ⚠️
src/backend/base/langflow/api/v1/projects.py 0.00% 1 Missing ⚠️
src/backend/base/langflow/utils/flow_secrets.py 97.77% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##           release-1.13.0   #15761      +/-   ##
==================================================
- Coverage           70.44%   69.93%   -0.52%     
==================================================
  Files                2786     2783       -3     
  Lines              301831   302049     +218     
  Branches            41012    43906    +2894     
==================================================
- Hits               212638   211239    -1399     
- Misses              86696    88315    +1619     
+ Partials             2497     2495       -2     
Flag Coverage Δ
backend 78.91% <94.18%> (+0.07%) ⬆️
frontend 67.01% <ø> (-0.94%) ⬇️
lfx 67.61% <ø> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...ackend/base/langflow/api/utils/mcp/flow_secrets.py 76.85% <97.05%> (+2.62%) ⬆️
...ckend/base/langflow/api/v1/flow_conflict_routes.py 78.43% <0.00%> (ø)
src/backend/base/langflow/api/v1/flows.py 65.18% <80.00%> (+0.59%) ⬆️
src/backend/base/langflow/api/v1/projects.py 54.76% <0.00%> (ø)
src/backend/base/langflow/utils/flow_secrets.py 91.00% <97.77%> (+0.68%) ⬆️

... and 354 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Oct 14, 2026
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Frontend Unit Test Coverage Report

Coverage Summary

Lines Statements Branches Functions
Coverage: 60%
60.18% (99822/165857) 74.66% (15814/21181) 55.62% (2496/4487)

Unit Test Results

Tests Skipped Failures Errors Time
7572 0 💤 0 ❌ 0 🔥 27m 1s ⏱️

@Jkavia
Jkavia requested a review from erichare October 15, 2026 13:21
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Oct 15, 2026

@erichare erichare left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@Jkavia I pushed 5d6e0af to fix both review findings. Header overrides now have stable flow/component scopes, including nested flows, and missing custom-header bindings fail strict snapshot validation. Regressions cover separate components and flows, copied components, repeated literal saves, and both header storage shapes.

Validation: 383 affected-route tests passed, 1 skipped. The deployment/persistence test run also passed all 331 tests. Pre-commit checks passed. Approving this updated head. CI has been queued for the new commit.

@github-actions github-actions Bot added lgtm This PR has been approved by a maintainer bug Something isn't working and removed bug Something isn't working labels Oct 15, 2026
@Jkavia
Jkavia enabled auto-merge October 15, 2026 14:06
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Oct 15, 2026
Janardan S Kavia added 2 commits October 15, 2026 16:26
CI caught this: `test_public_flow_read_strips_secrets` asserts a non-secret
header survives a public read, and it stopped surviving.

    X-Tenant: acme
      before this commit -> MCP_TENANT_MCP_HEADER_OVERRIDE_X_TENANT_9EFC07A6
      after              -> acme

    Authorization: Bearer ghp_real
      unchanged -> MCP_GITHUB_HEADER_OVERRIDE_AUTHORIZATION_4B0ECCD7, declared

The two halves of the Headers-box handling disagreed about which headers are
credentials, and I resolved it the wrong way round. Save time was widened to the
rule a *server config* follows -- everything outside `NON_SECRET_HEADERS` is a
secret -- which is right there, because a config is a connection's own
credentials. Applied to the component's Headers box it swept up ordinary routing
headers, so a tenant id became a variable somebody had to provision on the
serving plane for a value that is not a secret.

Narrowed instead: both halves now ask `is_secret_header_name`, one predicate so
they cannot drift apart again. A header that does not read as a credential is
left alone at save and at packaging, which is what that test has always said
should happen.

The test I added asserting the opposite is replaced by one asserting this.

Found by CI in a directory my own sweep skipped. The test reaches this code
through `langflow.api.utils.core`, so it names neither the function I edited nor
its module, and no grep for either would have found it. Grepping for the public
names the module exports does; that is how the 14 files run for this commit were
chosen, 600 passing.
Takes the per-node scoping and the explicit null for a rejected reference, and
holds both sides to `_is_secret_name` instead of `NON_SECRET_HEADERS`.

Treating every header outside `NON_SECRET_HEADERS` as a credential is the right
rule for a server *config*, where the whole map is operator-supplied auth. In the
component's own Headers box it swept up routing data: `X-Tenant: acme` became a
variable someone had to provision on the serving plane for a value that is not a
secret. `test_public_flow_read_strips_secrets` already asserted the narrower rule,
and was red on the branch.

The two tests that used `X-Tenant` to stand for a custom credential header now use
`X-Api-Key`, which is custom and reads as a credential, so what they check is intact.
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Oct 15, 2026
@Jkavia
Jkavia added this pull request to the merge queue Oct 15, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 15, 2026
@erichare
erichare merged commit a031c08 into release-1.13.0 Oct 15, 2026
121 checks passed
@erichare
erichare deleted the fix/mcp-tools-header-credentials branch October 15, 2026 21:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working lgtm This PR has been approved by a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants