Skip to content

fix(reborn): allow installed-local MCP over loopback HTTP - #6033

Closed
serrrfirat wants to merge 7 commits into
mainfrom
codex/fix-reborn-local-mcp-loopback
Closed

serrrfirat wants to merge 7 commits into
mainfrom
codex/fix-reborn-local-mcp-loopback

Conversation

@serrrfirat

@serrrfirat serrrfirat commented Jul 13, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Allow installed-local MCP servers to use plaintext HTTP only at an exact literal IPv4 loopback endpoint.
  • Keep host-bundled MCP HTTPS-only and continue denying hostname/IPv6/LAN/remote HTTP, registry-installed MCP, and stdio.
  • Carry the exact endpoint through lifecycle grants and runtime egress, with unit, contract, and production-composed integration coverage.
  • Resolve the PR against current main, preserving the new ironclaw_extension_host ownership boundary.

Change Type

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • CI/Infrastructure
  • Security
  • Dependencies

Linked Issue

Fixes #5998

Validation

  • cargo fmt --all -- --check
  • cargo clippy --all --benches --tests --examples --all-features -- -D warnings
  • cargo build
  • Relevant tests pass: extension parsing/policy, extension-host MCP planning, full MCP integration suite, architecture tests, and Reborn architecture E2E
  • cargo test --features integration if database-backed or integration behavior changed
  • Manual testing: hermetic loopback MCP transcript verified initialize, initialized notification, exact tools/call, no Authorization header, and persisted final reply
  • If a coding agent was used and supports it, review-pr or pr-shepherd --fix was run before requesting review

Targeted warning-as-error lint was run for every affected package:

cargo clippy -p ironclaw_extensions -p ironclaw_extension_host -p ironclaw_reborn_composition -p ironclaw_reborn_integration_tests --all-targets --all-features -- -D warnings

Test Strategy

User behavior: An installed-local MCP process can be called over http://127.x.x.x:<exact-port>/<exact-path> without TLS. All broader plaintext or non-local variants still fail closed.

Risk areas:

  • Model behavior
  • Browser
  • Side effect
  • Persistence
  • Security or permissions
  • External provider
  • Cross-component behavior

Tests added or updated:

  • Unit or contract: canonical MCP endpoint parsing, source/transport policy, exact URL matching, default-port pinning, credential planning, lifecycle grant propagation, and private-range exception scoping.
  • Reborn integration: production-composed installed-local MCP dispatch through activation, authorization, runtime planning, mediated HTTP, tool result, and persisted reply.
  • Recorded fixture: Not applicable: behavior is deterministic and uses an in-process HTTP server rather than a recorded model/provider fixture.
  • Browser E2E: Not applicable: no browser or WebUI behavior changes.
  • Backend or runtime: full MCP integration suite, full architecture package, and scripts/reborn-e2e-rust.sh architecture.
  • Live canary: Not applicable: the security contract is fully exercised hermetically and does not require live credentials or a third-party service.

What the tests prove: Only installed-local literal IPv4 loopback HTTP receives the narrow exception; the endpoint remains exact by scheme/host/port/path; no credential is injected for the public local fixture; hosted HTTPS behavior and MCP error/SSE paths still work; ownership boundaries remain intact.

Commands run:

  • cargo fmt --all -- --check
  • cargo test -p ironclaw_extensions
  • cargo test -p ironclaw_extension_host mcp
  • cargo test -p ironclaw_reborn_integration_tests --test reborn_integration_mcp installed_local_mcp_dispatches_through_production_composition -- --exact --nocapture
  • cargo test -p ironclaw_reborn_integration_tests --test reborn_integration_mcp
  • cargo test -p ironclaw_architecture
  • cargo clippy -p ironclaw_extensions -p ironclaw_extension_host -p ironclaw_reborn_composition -p ironclaw_reborn_integration_tests --all-targets --all-features -- -D warnings
  • bash scripts/reborn-e2e-rust.sh architecture

Security Impact

This intentionally widens network access for one narrowly identified case: an InstalledLocal MCP manifest using plaintext HTTP to a literal IPv4 loopback address. The grant and runtime planner both pin the exact scheme, address, port, and path. localhost, IPv6, LAN/private non-loopback, remote HTTP, RegistryInstalled, host-bundled loopback HTTP, and stdio remain denied. The runtime response remains capped at 2 MiB.

Reborn Trust-Boundary Checklist

  • Public policy/evidence/trust-bearing types: the manifest parser constructs the canonical endpoint; lifecycle code derives the exact network target from the active installed-local package.
  • Untrusted content enters prompts only through an envelope/escaping primitive. N/A: no prompt construction changed.
  • Hashes declare purpose; trust/binding/authenticity uses SHA-256/BLAKE3 or separate authenticity check. N/A: no hashes changed.
  • New/changed status, exit, policy, runtime, or error variants: downstream match sites audited. Command/output: no enum variants added; exhaustive source/transport match is covered by tests.
  • Security/durability serde(default) fields fail closed or have migration tests. N/A: no persisted or serde-default field added.
  • Queues/maps/buffers/counters have bounds and overflow-safe arithmetic. MCP response egress is capped at 2 MiB.
  • Driver/operator-visible errors have stable class semantics (Transient, Permanent, Misconfigured, PolicyDenied or equivalent). Disallowed endpoints continue to produce policy denial.
  • Sandbox/native/host names accurately describe trust boundary. InstalledLocal, HostBundled, and RegistryInstalled remain distinct.

Database Impact

None. No migrations or persistence schema changes.

Blast Radius

Touches MCP manifest parsing, extension lifecycle capability metadata, local-dev grant policy, MCP egress planning, and the Reborn integration harness. Hosted MCP remains on the existing HTTPS path.

Rollback Plan

Revert the local-loopback MCP commits. Installed-local plaintext MCP calls will return to failing closed; there is no data migration or persisted compatibility step to undo.

Review Follow-Through

Merge conflicts were resolved by keeping current main ownership in ironclaw_extension_host. The current review focus is the narrowness of the loopback exception and parity between grant-time and dispatch-time endpoint policy.


Review track: C (security/runtime)

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6033 July 13, 2026 10:31 Destroyed
@github-actions github-actions Bot added scope: docs Documentation size: L 200-499 changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Jul 13, 2026
@coderabbitai

coderabbitai Bot commented Jul 13, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e71a105e-2d66-4278-a072-4a5bba0a6314

📥 Commits

Reviewing files that changed from the base of the PR and between 17300ed and b806713.

📒 Files selected for processing (2)
  • crates/ironclaw_extensions/src/lib.rs
  • tests/integration/support/builder.rs

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Allowed installed-local MCP servers over plaintext HTTP only when the manifest targets an exact literal IPv4 loopback endpoint, with network egress locked to that scheme/IP/port (defaulting to 80 when omitted).
  • Bug Fixes
    • Enforced HTTPS-only for remote and host-bundled MCP.
    • Rejected unsafe or noncompliant HTTP endpoint forms (userinfo, query, fragments), plus localhost (DNS), IPv6, non-loopback private/LAN, registry-installed loopback, and stdio-based MCP.
  • Documentation
    • Updated MCP setup guidance and runtime/security contracts to match the new endpoint policy.
  • Tests
    • Added end-to-end and regression coverage for installed-local MCP dispatch and endpoint restrictions.

Walkthrough

Reborn adds installed-local MCP dispatch over plaintext HTTP at literal IPv4 loopback endpoints. Canonical endpoint parsing, exact egress policies, production-composition integration coverage, and MCP transport documentation were updated; hosted MCP discovery remains HTTPS-only.

Changes

Installed-local MCP loopback transport

Layer / File(s) Summary
Canonical endpoint contract
crates/ironclaw_extensions/src/mcp_http_endpoint.rs, crates/ironclaw_extensions/src/hosted_mcp_discovery.rs, crates/ironclaw_extensions/src/lib.rs
Adds canonical endpoint parsing and matching, permits loopback HTTP, and keeps hosted discovery HTTPS-only.
Capability network policy
crates/ironclaw_reborn_composition/src/runtime/local_dev/extension_surface.rs, crates/ironclaw_reborn_composition/src/runtime/approval.rs
Adds an exact local loopback target override and updates capability fixtures.
MCP egress planning
crates/ironclaw_extension_host/src/mcp.rs
Adds source-scoped endpoint selection, exact URL matching, port pinning, credential-audience handling, and loopback-specific network policies.
Production-composition integration path
tests/integration/support/*, tests/integration/mcp.rs
Stages installed-local manifests and installation records, adds an installed-local MCP harness path, and verifies loopback tool dispatch without authorization headers.
MCP transport documentation
FEATURE_PARITY.md, docs/extensions/*, docs/reborn/*, crates/ironclaw_extensions/AGENTS.md, crates/ironclaw_reborn_composition/CLAUDE.md, crates/ironclaw_architecture/tests/*
Documents endpoint restrictions, exact egress matching, port pinning, unsupported stdio dispatch, and updated implementation locations.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RebornIntegrationHarnessBuilder
  participant HostRuntimeCapabilityHarness
  participant MCP Egress Planner
  participant MockMCPServer
  RebornIntegrationHarnessBuilder->>HostRuntimeCapabilityHarness: configure installed-local MCP
  HostRuntimeCapabilityHarness->>MCP Egress Planner: register manifest and derive exact loopback policy
  MCP Egress Planner->>MockMCPServer: dispatch tools/call
  MockMCPServer-->>HostRuntimeCapabilityHarness: return tool result
Loading

Possibly related issues

Possibly related PRs

Suggested reviewers: henrypark133

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The conventional title matches the main change: enabling installed-local MCP over loopback HTTP in Reborn.
Description check ✅ Passed The description fills the required sections with summary, change type, linked issue, validation, test strategy, security, and rollback details.
Linked Issues check ✅ Passed The code implements #5998’s requested loopback-HTTP interim while keeping HTTPS-only remote MCP and stdio denied.
Out of Scope Changes check ✅ Passed The changes stay on the MCP transport/policy/doc/test path and do not introduce clear unrelated scope.

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.

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

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.

Code Review

This pull request adds support for plaintext HTTP Model Context Protocol (MCP) servers when the URL host is a literal IPv4 loopback address, while continuing to reject localhost, private LAN addresses, and plaintext remote endpoints. It updates the capability mapping, network policies, and documentation to reflect this change. The review feedback suggests two improvements: removing a redundant filter check in installed_local_mcp_loopback_target and centralizing the duplicated literal_loopback_host helper function to avoid code duplication across crates.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +185 to +193
pub(crate) fn installed_local_mcp_loopback_target(
package: &ExtensionPackage,
) -> Option<NetworkTargetPattern> {
(package.manifest.source == ManifestSource::InstalledLocal)
.then(|| hosted_http_mcp_endpoint(package))
.flatten()
.filter(|endpoint| endpoint.is_loopback_http)
.map(|endpoint| endpoint.network_target())
}

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

The .filter(|endpoint| endpoint.is_loopback_http) check is redundant here because hosted_http_mcp_endpoint already filters out non-loopback HTTP endpoints for InstalledLocal packages. We can safely remove this filter to simplify the code.

pub(crate) fn installed_local_mcp_loopback_target(
    package: &ExtensionPackage,
) -> Option<NetworkTargetPattern> {
    (package.manifest.source == ManifestSource::InstalledLocal)
        .then(|| hosted_http_mcp_endpoint(package))
        .flatten()
        .map(|endpoint| endpoint.network_target())
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed; no code change: INCORRECT — the repeated loopback predicate is deliberate defense-in-depth at the private-range exception boundary, so future endpoint-policy changes cannot silently widen the exception.

Comment on lines +112 to +117
fn literal_loopback_host(url: &url::Url) -> bool {
match url.host() {
Some(url::Host::Ipv4(address)) => address.is_loopback(),
Some(url::Host::Ipv6(_) | url::Host::Domain(_)) | None => 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

The literal_loopback_host helper function is duplicated here and in crates/ironclaw_reborn_composition/src/extension_host/mcp.rs. To maintain drift-resistance and avoid duplicating domain knowledge locally, consider making this function public and centralizing it so both crates can reuse the same implementation.

References
  1. Prefer routing through centralized helper functions that define domain boundaries or strip transient fields (e.g., credential_owner_scope()) to maintain drift-resistance and avoid duplicating domain knowledge locally, even if it introduces minor performance overhead (like cloning) on non-hot paths.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed; no code change: ALREADY ADDRESSED — McpHttpEndpoint now owns the canonical literal-IPv4-loopback predicate and the runtime planner delegates to it.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ironclaw_extensions/src/hosted_mcp_discovery.rs`:
- Around line 112-117: Centralize the literal_loopback_host implementation so
only one canonical helper performs the IPv4 loopback check. Keep or move the
helper from crates/ironclaw_extensions/src/hosted_mcp_discovery.rs:112-117 into
a shared location, then replace the duplicate in
crates/ironclaw_reborn_composition/src/extension_host/mcp.rs:202-207 with an
import and update its callers; preserve the existing behavior for IPv6, domain
hosts, and absent hosts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f01d32d2-0a5e-4637-a6c6-d2913d59c201

📥 Commits

Reviewing files that changed from the base of the PR and between c23277f and ef87044.

📒 Files selected for processing (10)
  • FEATURE_PARITY.md
  • crates/ironclaw_extensions/src/hosted_mcp_discovery.rs
  • crates/ironclaw_reborn_composition/src/extension_host/extension_lifecycle.rs
  • crates/ironclaw_reborn_composition/src/extension_host/mcp.rs
  • crates/ironclaw_reborn_composition/src/factory.rs
  • crates/ironclaw_reborn_composition/src/runtime/approval.rs
  • crates/ironclaw_reborn_composition/src/runtime/local_dev/extension_surface.rs
  • docs/capabilities/mcp.md
  • docs/extensions/building-a-tool.md
  • docs/reborn/contracts/mcp.md

Comment thread crates/ironclaw_extensions/src/hosted_mcp_discovery.rs Outdated
@github-actions

github-actions Bot commented Jul 13, 2026 •

Copy link
Copy Markdown
Contributor

Coverage ratchet

Ratchet mode: ENFORCING

RATCHET PASS: global
  observed: 85.67% (311084 / 363131 lines)
  floor:    80.81% (tolerance 0.5pp -> effective floor 80.31%)
  denominator: 363131 lines now vs 377084 at floor capture (-13953 lines, -3.7%) — not a material change

⚠️ 2 Reborn crate(s) have 0 int-tier coverage (target: 0) — ironclaw_prompt_envelope, ironclaw_scripts

Reborn integration-tier coverage

Line coverage (Reborn crates): 85.67% — 311084 / 363131 lines

Per-crate breakdown (60 crates, lowest-covered first)
Crate Line % Covered / Total
ironclaw_prompt_envelope 0% 0 / 88
ironclaw_scripts 0% 0 / 345
ironclaw_process_sandbox 33.91% 118 / 348
ironclaw_host_ingress 42.5% 17 / 40
ironclaw_event_projections 43.71% 684 / 1565
ironclaw_observability 61.54% 16 / 26
ironclaw_telegram_v2_adapter 62.35% 631 / 1012
ironclaw_authorization 62.98% 609 / 967
ironclaw_memory 64.41% 959 / 1489
ironclaw_trust 73.21% 664 / 907
ironclaw_filesystem 74.31% 4749 / 6391
ironclaw_wasm_limiter 74.6% 47 / 63
ironclaw_extractors 74.72% 538 / 720
ironclaw_capabilities 75.45% 2879 / 3816
ironclaw_projects 76.48% 400 / 523
ironclaw_mcp 76.6% 779 / 1017
ironclaw_reborn_cli 78.22% 10693 / 13670
ironclaw_telegram_extension 78.59% 962 / 1224
ironclaw_wasm 79.72% 735 / 922
ironclaw_llm 79.98% 22014 / 27523
ironclaw_memory_native 81.02% 3299 / 4072
ironclaw_auth 81.88% 6679 / 8157
ironclaw_first_party_extensions 82.38% 6682 / 8111
ironclaw_events 82.47% 1604 / 1945
ironclaw_host_api 82.98% 9419 / 11351
ironclaw_processes 83.3% 933 / 1120
ironclaw_reborn_identity 83.8% 450 / 537
ironclaw_operator 84.37% 5558 / 6588
ironclaw_secrets 84.56% 2798 / 3309
ironclaw_reborn_config 85.23% 2101 / 2465
ironclaw_skills 85.29% 4494 / 5269
ironclaw_extension_host 85.55% 19049 / 22266
ironclaw_run_state 85.77% 458 / 534
ironclaw_reborn_composition 85.78% 24619 / 28700
ironclaw_triggers 85.92% 2783 / 3239
ironclaw_webui 86.1% 10918 / 12680
ironclaw_reborn_event_store 86.51% 1251 / 1446
ironclaw_network 86.53% 919 / 1062
ironclaw_hooks 86.63% 9935 / 11468
ironclaw_common 86.99% 1772 / 2037
ironclaw_approvals 87.18% 1543 / 1770
ironclaw_threads 87.2% 4851 / 5563
ironclaw_extensions 87.34% 3754 / 4298
ironclaw_product 87.74% 20169 / 22986
ironclaw_reborn_traces 88.13% 11986 / 13600
ironclaw_turns 88.41% 14384 / 16270
ironclaw_slack_extension 88.47% 1934 / 2186
ironclaw_host_runtime 88.93% 19933 / 22413
ironclaw_reborn_openai_compat 89.32% 3780 / 4232
ironclaw_conversations 90.01% 3164 / 3515
ironclaw_resources 90.84% 4474 / 4925
ironclaw_event_streams 91.24% 1063 / 1165
ironclaw_runner 91.34% 17304 / 18944
ironclaw_loop_host 91.89% 16540 / 18000
ironclaw_attachments 93.06% 630 / 677
ironclaw_outbound 93.91% 4101 / 4367
ironclaw_agent_loop 94.65% 9916 / 10477
ironclaw_safety 95.28% 3858 / 4049
ironclaw_first_party_extension_ports 95.62% 3672 / 3840
ironclaw_runtime_policy 96.56% 813 / 842

This table itself is informational and never gates the PR on its own — not the percentage, not the per-crate holes, not the 0-coverage callout. A separate coverage ratchet (dry-run until enforce=true; see tests/integration/coverage-floor.toml) can fail the build on specific configured floors.

Exemptions (3 entry/entries excluded from the accounting above)
Module / Crate Reason Issue
crate: ironclaw_embeddings v1-only: consumed only by root ironclaw (src/app.rs, src/tools/builtin/memory.rs, src/workspace/mod.rs, src/config/{mod,embeddings}.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_gateway v1-only: consumed only by root ironclaw (src/channels/web/platform/static_files.rs, src/channels/web/handlers/frontend.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_tui v1-only: consumed only by root ironclaw (src/main.rs, src/channels/tui.rs); no crates/* dependents. Crate's own doc comment confirms it bridges INTO v1, not Reborn. Covered by "Tests (Legacy)". #5657

@railway-app

railway-app Bot commented Jul 13, 2026 •

Copy link
Copy Markdown

🚅 Deployed to the ironclaw-pr-6033 environment in ironclaw-ci-preview

Service Status Web Updated (UTC)
ironclaw ❌ Build Failed (View Logs) Web Jul 28, 2026 at 3:59 pm

@serrrfirat serrrfirat left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review (multi-agent)

Intent: Restore local MCP connectivity in Reborn by permitting plaintext HTTP only for tightly pinned literal IPv4 loopback endpoints installed locally.

Shape: normal, no modifiers — a single non-stacked PR; 432 changed lines across 10 files, below mega/generated/mechanical thresholds.

Coverage: complete from an exact local-git comparison (c23277f...ef87044); buckets: 6 production, 4 docs, 0 tests, 0 generated/vendor, 0 CI, 0 config; no packetization; no oversized files; no failed reviewers or limitations.

Stats: 8 findings from 10 raw, 8 after overlap/same-line dedup, across 2 files. Reviewers run: security, bugs, performance, tests, conventions, local-patterns, maintainability, approach. Reviewers failed: none. Body-only: 0.

Conventions

  1. Medium Restrict HostBundled endpoints to HTTPS (crates/ironclaw_reborn_composition/src/extension_host/mcp.rs:178-181, confidence 100) — anchor: FEATURE_PARITY.md:340
    The source match accepts loopback HTTP for every host-bundled package, exceeding the advertised InstalledLocal-only trust boundary. Also flagged by Approach and Bugs.
  2. Medium Production MCP policy change lacks integration coverage (crates/ironclaw_reborn_composition/src/extension_host/mcp.rs:178-192, confidence 100) — anchor: AGENTS.md:100-102
    The production-wired source → lifecycle → grant → private-range policy path has only crate-local tests, contrary to the repository's Integration-First rule.

Tests

  1. Medium Installed-local MCP dispatch lacks production-path integration coverage (crates/ironclaw_reborn_composition/src/runtime/local_dev/extension_surface.rs:183-190, confidence 100) — anchor: extension_surface.rs:183
    Existing integration coverage manually supplies a planner and policy, so it cannot catch failures in the newly changed production composition path.
  2. Medium Cover denied RegistryInstalled source (crates/ironclaw_reborn_composition/src/extension_host/mcp.rs:178-181, confidence 100) — anchor: mcp.rs:181
    The explicit RegistryInstalled denial has no regression test using an otherwise valid loopback endpoint.
  3. Low Composition-side IPv6 loopback rejection is untested (crates/ironclaw_reborn_composition/src/extension_host/mcp.rs:202-205, confidence 100) — anchor: mcp.rs:205
    The composition-owned parser separately rejects IPv6, but its caller-level rejection cases omit http://[::1].
  4. Low Default-port pinning is tested only below the planner caller (crates/ironclaw_reborn_composition/src/extension_host/mcp.rs:148-156, confidence 100) — anchor: mcp.rs:155
    The helper test cannot detect the planner emitting an unpinned port: None for an omitted-port endpoint.

Local Patterns

  1. Low Rename the endpoint type now that it also represents installed-local MCP (crates/ironclaw_reborn_composition/src/extension_host/mcp.rs:106, confidence 75) — anchor: mcp.rs:164-180
    HostedMcpEndpoint now represents both host-bundled and installed-local endpoints, making the source-policy boundary harder to navigate.

Maintainability

  1. Medium Centralize MCP endpoint admission in the manifest owner (crates/ironclaw_extensions/src/hosted_mcp_discovery.rs:98-117, confidence 95) — anchor: hosted_mcp_discovery.rs:98
    Two crates now independently implement the same security-sensitive URL-shape and literal-loopback predicate, creating drift risk between discovery and dispatch.

if let Some(policy) = gsuite_network_policy_for(&capability.provider) {
return policy;
}
if let Some(target) = &capability.local_mcp_loopback_target {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Medium — Installed-local MCP dispatch lacks production-path integration coverage.

No test in tests/integration/ installs and activates an InstalledLocal loopback MCP package and dispatches its capability through production composition. Existing tests/integration/mcp.rs coverage uses a HostBundled test package, StaticMcpHostHttpEgressPlanner, and a manually supplied loopback policy that explicitly bypasses the production lifecycle, egress planner, and network-policy path changed here. It therefore cannot catch failures to propagate local_mcp_loopback_target, mint the private-range exception, or reach the server through the newly enabled user-visible flow.

Fix: tests::integration::mcp::installed_local_loopback_mcp_dispatch_reaches_server_through_production_composition covering install, activation, scripted tool dispatch, and a recorded tools/call at the loopback server

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed; no code change: ALREADY ADDRESSED — installed_local_mcp_dispatches_through_production_composition now installs the local package, dispatches through production composition, and asserts the recorded MCP wire call and model-visible result. Verification: the targeted integration test passes.

match package.manifest.source {
ManifestSource::HostBundled => Some(endpoint),
ManifestSource::InstalledLocal if endpoint.is_loopback_http => Some(endpoint),
ManifestSource::InstalledLocal | ManifestSource::RegistryInstalled => None,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Medium — Cover denied RegistryInstalled source.

The new source gate explicitly denies RegistryInstalled packages, and the PR claims this invariant, but the rejection tests construct every denied package as InstalledLocal. No adjacent or integration test exercises a registry-installed package using an otherwise valid literal-loopback HTTP endpoint, so a regression that grants this source the local exception would pass.

Fix: tests::extension_host::mcp::planner_rejects_registry_installed_loopback_http covering a RegistryInstalled package at http://127.0.0.1:4321/mcp and asserting an empty egress policy

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed; no code change: ALREADY ADDRESSED — planner_denies_registry_installed_loopback_mcp exercises an otherwise valid literal-loopback endpoint and confirms no egress target is granted.

fn literal_loopback_host(url: &url::Url) -> bool {
match url.host() {
Some(url::Host::Ipv4(address)) => address.is_loopback(),
Some(url::Host::Ipv6(_) | url::Host::Domain(_)) | None => false,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Low — Composition-side IPv6 loopback rejection is untested.

The composition crate has its own literal_loopback_host implementation, separate from the similarly named function tested in ironclaw_extensions. Its planner rejection cases cover localhost, LAN IPv4, and remote HTTPS but omit http://[::1], so the explicit IPv6-denial constraint is not protected at the runtime egress planner that controls dispatch.

Fix: tests::extension_host::mcp::planner_rejects_installed_local_ipv6_loopback_http covering http://[::1]:4321/mcp and asserting no allowed target or credential injection

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed; no code change: ALREADY ADDRESSED — the planner caller rejection table now includes http://[::1]:4321/mcp and confirms the endpoint remains denied.

&& target.host_pattern.eq_ignore_ascii_case(&self.host_pattern)
&& target.port == self.port
}

fn matches_url(&self, url: &str) -> bool {
Self::parse(url).is_some_and(|request_endpoint| request_endpoint == *self)
}

fn network_target(&self) -> NetworkTargetPattern {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Low — Default-port pinning is tested only below the planner caller.

The omitted-port test calls installed_local_mcp_loopback_target directly and asserts Some(80), while the side-effect-gating caller is RegistryMcpEgressPlanner::plan. The explicit-port planner test cannot detect a regression where the planner stops using the pinned target and emits port: None only for omitted-port URLs. A caller-level planner test is required by the repository's testing rule for transforms that gate network side effects.

Fix: tests::extension_host::mcp::planner_pins_omitted_loopback_http_port_to_80 covering a manifest and request at http://127.0.0.1/mcp and asserting the planner emits exactly port 80 with the private-range exception

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed; no code change: ALREADY ADDRESSED — planner_pins_installed_local_default_http_port drives RegistryMcpEgressPlanner::plan and asserts an omitted loopback HTTP port becomes exactly 80.

HostedMcpEndpoint::parse(url)
let endpoint = HostedMcpEndpoint::parse(url)?;
match package.manifest.source {
ManifestSource::HostBundled => Some(endpoint),

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Medium — Restrict HostBundled endpoints to HTTPS.

HostedMcpEndpoint::parse accepts literal-loopback HTTP, and this source match returns that endpoint for every HostBundled package. hosted_mcp_discovery.rs:102-104 independently enables the same host-bundled path. That conflicts with the stated installed-local-only scope recorded in FEATURE_PARITY.md:340 and the user-facing support statement in docs/capabilities/mcp.md:10-13, expanding the source/trust contract beyond the advertised behavior.

Fix: Reject HTTP endpoints for ManifestSource::HostBundled in both runtime planning and hosted discovery, leaving the loopback exception exclusively for InstalledLocal.

Also flagged by: approach/Medium, bugs/Medium

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed; no code change: ALREADY ADDRESSED — host-bundled loopback HTTP is rejected and covered by a planner caller test; the plaintext exception remains InstalledLocal-only.

return None;
}
HostedMcpEndpoint::parse(url)
let endpoint = HostedMcpEndpoint::parse(url)?;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Medium — Production MCP policy change lacks integration coverage.

The assigned diff changes the production-wired path from extension source and runtime URL through capability grants, private-range policy, and MCP HTTP egress, but adds only crate-local tests. No tests/integration/ test drives an InstalledLocal manifest through the composed runtime to a loopback MCP server, and the PR body gives no reason that the integration tier cannot reach this path. Existing tests/integration/mcp.rs covers a directly wired mock MCP backend, not the new installation-source/lifecycle/policy projection.

Fix: Extend the Reborn MCP integration suite to install or restore an InstalledLocal loopback MCP package, invoke it through the composed runtime, and assert both the recorded HTTP call and exact network-policy seam.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed; no code change: DUPLICATE and ALREADY ADDRESSED by the production-composed installed-local MCP integration test, which verifies dispatch, the wire request, and the returned tool result.

NetworkTargetPattern {
scheme: Some(self.scheme),
host_pattern: self.host_pattern.clone(),
// NetworkTargetPattern::port = None matches any port. Pin the HTTP

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Low — Rename the endpoint type now that it also represents installed-local MCP.

HostedMcpEndpoint and hosted_http_mcp_endpoint now represent both HostBundled and InstalledLocal packages, while the accompanying documentation deliberately broadens the concept to HTTP MCP. Keeping the old hosted-only names makes source-policy checks harder to navigate and invites future callers to assume this parser is restricted to host-bundled providers.

Fix: Rename the internal endpoint vocabulary consistently, for example HttpMcpEndpoint, http_mcp_endpoint, mcp_http_url_allowed, and mcp_http_network_policy, while retaining explicitly hosted names only for schema-discovery behavior that remains host-bundled.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed; no code change: ALREADY ADDRESSED — the canonical shared type is now McpHttpEndpoint, while McpEgressEndpoint is the accurately named runtime-policy wrapper.

}

#[test]
fn hosted_mcp_recognizes_plaintext_only_for_literal_loopback() {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Medium — Centralize MCP endpoint admission in the manifest owner.

The accepted MCP URL shape is now independently implemented here and in extension_host/mcp.rs:114-207, including an identical literal-IPv4-loopback predicate. Discovery and runtime policy must agree on this security-sensitive fact, but future endpoint changes currently require editing two parsers in different crates; divergence would let a package pass discovery while being rejected or interpreted differently at dispatch.

Fix: Define one typed, side-effect-free HTTP MCP endpoint parser in ironclaw_extensions, which owns declarative runtime metadata, and have both hosted discovery and Reborn composition consume its parsed result. Keep source-specific admission at callers while deleting duplicate URL component checks.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed; no code change: ALREADY ADDRESSED — endpoint parsing and exact URL identity now live in the manifest-owning ironclaw_extensions::McpHttpEndpoint; source-specific admission remains at host callers.

@serrrfirat
serrrfirat force-pushed the codex/fix-reborn-local-mcp-loopback branch from ef87044 to 9a7a0db Compare July 13, 2026 11:53
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6033 July 13, 2026 11:53 Destroyed
@github-actions github-actions Bot added size: XL 500+ changed lines and removed size: L 200-499 changed lines labels Jul 13, 2026
@serrrfirat serrrfirat changed the title fix(reborn): allow local loopback MCP HTTP fix(reborn): allow installed-local MCP over loopback HTTP Jul 13, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ironclaw_reborn_composition/src/extension_host/mcp.rs`:
- Around line 127-131: Update allows_target to treat an audience port of None as
a wildcard and normalize omitted HTTP ports to the default port before comparing
with self.parsed.port. Preserve scheme and host matching, and add
RegistryMcpEgressPlanner::plan coverage for credential injection with both
explicit and omitted loopback ports.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 68e8504d-2bcf-4a69-80fe-2589c06c5ecc

📥 Commits

Reviewing files that changed from the base of the PR and between ef87044 and 9a7a0db.

⛔ Files ignored due to path filters (4)
  • CHANGELOG.md is excluded by !CHANGELOG.md
  • docs/zh/extensions/mcp.mdx is excluded by !docs/zh/**
  • tests/fixtures/installed_local_mcp/schemas/mock-mcp/mock.input.v1.json is excluded by !tests/fixtures/**
  • tests/fixtures/installed_local_mcp/schemas/mock-mcp/mock.output.v1.json is excluded by !tests/fixtures/**
📒 Files selected for processing (29)
  • FEATURE_PARITY.md
  • crates/ironclaw_extensions/AGENTS.md
  • crates/ironclaw_extensions/src/hosted_mcp_discovery.rs
  • crates/ironclaw_extensions/src/lib.rs
  • crates/ironclaw_extensions/src/mcp_http_endpoint.rs
  • crates/ironclaw_reborn_composition/CLAUDE.md
  • crates/ironclaw_reborn_composition/src/extension_host/extension_lifecycle.rs
  • crates/ironclaw_reborn_composition/src/extension_host/mcp.rs
  • crates/ironclaw_reborn_composition/src/factory.rs
  • crates/ironclaw_reborn_composition/src/runtime/approval.rs
  • crates/ironclaw_reborn_composition/src/runtime/local_dev/extension_surface.rs
  • docs/capabilities/mcp.md
  • docs/extensions/building-a-tool.md
  • docs/extensions/mcp.mdx
  • docs/internal/2026-06-26-legacy-vs-reborn-feature-comparison.md
  • docs/reborn/contracts/extensions.md
  • docs/reborn/contracts/host-runtime.md
  • docs/reborn/contracts/mcp.md
  • docs/reborn/contracts/runtime-profiles.md
  • docs/reborn/contracts/runtime-selection.md
  • docs/reborn/how-to-port-tool-to-reborn.md
  • tests/integration/CLAUDE.md
  • tests/integration/mcp.rs
  • tests/integration/support/builder.rs
  • tests/integration/support/capability_backend.rs
  • tests/integration/support/harness/mod.rs
  • tests/integration/support/harness/options.rs
  • tests/integration/support/harness/profiles/mock_mcp.rs
  • tests/integration/support/harness_mcp.rs

Comment thread crates/ironclaw_extension_host/src/mcp.rs
…t-fix

# Conflicts:
#	CHANGELOG.md
#	crates/ironclaw_extension_host/src/mcp.rs
#	crates/ironclaw_extensions/src/hosted_mcp_discovery.rs
#	crates/ironclaw_extensions/src/lib.rs
#	crates/ironclaw_reborn_composition/src/extension_host/extension_lifecycle.rs
#	crates/ironclaw_reborn_composition/src/factory.rs
#	crates/ironclaw_reborn_composition/src/runtime/approval.rs
#	crates/ironclaw_reborn_composition/src/runtime/local_dev/extension_surface.rs
#	docs/capabilities/mcp.md
#	docs/extensions/building-a-tool.md
#	docs/extensions/mcp.mdx
#	docs/internal/2026-06-26-legacy-vs-reborn-feature-comparison.md
#	docs/zh/extensions/mcp.mdx
#	tests/integration/support/harness/mod.rs
#	tests/integration/support/harness/options.rs
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6033 July 28, 2026 13:10 Destroyed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ironclaw_extension_host/src/mcp.rs`:
- Around line 773-776: Update the installed-local credential audience
construction around NetworkTargetPattern to use the endpoint scheme, permitting
HTTP only for InstalledLocal literal IPv4 loopback endpoints while preserving
HTTPS requirements elsewhere. Adjust the manifest validation in the relevant v2
audience validation flow to allow that same narrowly scoped HTTP case. Add
planner-level tests through the credential-injection caller covering
installed-local HTTP loopback endpoints with explicit port 80 and omitted port
80.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 543e1f7f-427e-4df8-81bc-fadf4e6cc621

📥 Commits

Reviewing files that changed from the base of the PR and between 3ab414c and a8eb6ef.

📒 Files selected for processing (1)
  • crates/ironclaw_extension_host/src/mcp.rs

Comment thread crates/ironclaw_extension_host/src/mcp.rs
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6033 July 28, 2026 13:25 Destroyed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/integration/mcp.rs (1)

358-363: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Scope the redaction assertions to the same subsequent model request.

assert_model_request_contains scans every captured request (tests/integration/support/assertions.rs:240-254), so the socket path and [REDACTED] assertions can succeed against different turns—or unrelated prompt content. This does not prove that the post-error request contains the sanitized cause as a bounded unit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/mcp.rs` around lines 358 - 363, Update the assertions
following the MCP backend error in the integration test to inspect the same
subsequent captured model request for both the socket path and “[REDACTED]”. Use
an assertion helper or request-specific capture that validates the sanitized
cause as one bounded unit, rather than calling assert_model_request_contains
separately across all captured requests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@tests/integration/mcp.rs`:
- Around line 358-363: Update the assertions following the MCP backend error in
the integration test to inspect the same subsequent captured model request for
both the socket path and “[REDACTED]”. Use an assertion helper or
request-specific capture that validates the sanitized cause as one bounded unit,
rather than calling assert_model_request_contains separately across all captured
requests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b76a3a0c-3bee-4980-b599-0a494dfeb7ef

📥 Commits

Reviewing files that changed from the base of the PR and between a8eb6ef and 17300ed.

⛔ Files ignored due to path filters (1)
  • CHANGELOG.md is excluded by !CHANGELOG.md
📒 Files selected for processing (1)
  • tests/integration/mcp.rs

@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Review follow-up

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6033 July 28, 2026 15:56 Destroyed

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-6033 — b8067133 Deployed Jul 28, 2026 by railway-app[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: low Changes to docs, tests, or low-risk modules scope: docs Documentation size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reborn has no transport for a local (on-device) MCP server: stdio is rejected, loopback HTTP is denied

1 participant