* fix(mcp): handle 202 Accepted for Streamable HTTP notifications
The MCP Streamable HTTP spec requires servers to respond with
202 Accepted (empty body) for JSON-RPC notifications like
`notifications/initialized`. The HTTP transport tried to parse
this empty body as JSON, which failed and broke the session
handshake — subsequent requests like `tools/list` were rejected
because the server considered the session uninitialized.
Add an early return for 202 responses that produces an empty
McpResponse without attempting body parsing.
Fixes#1436
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* fix(mcp): wire session manager into transport for non-OAuth HTTP clients
The factory used McpClient::new_with_config().with_session_manager()
which only set the session manager on the client, not on the
HttpMcpTransport. The transport never captured Mcp-Session-Id from
responses, so subsequent requests lacked the header and the server
rejected them as uninitialized.
Fix by constructing the HttpMcpTransport with the session manager
before wrapping it in Arc, matching the pattern already used by
new_authenticated().
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* refactor(mcp): deduplicate factory HTTP path, gate dead-code methods as test-only
- Collapse the two identical non-OAuth HTTP branches in
`create_client_from_config()` into one (early-return for the
authenticated path, fall through for the common case).
- Gate `McpClient::new_with_config()` and `McpClient::with_session_manager()`
as `#[cfg(test)]` — the factory was their only production caller and no
longer uses them. Both methods silently skip wiring the session manager
into the transport, which was the root cause of #1436.
- Add doc warnings on both methods explaining the footgun.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
Co-authored-by: [email protected] <[email protected]>
* fix(mcp): address 14 audit findings across MCP module
- Replace panicking assert! in new_with_config with Result return (Critical)
- Fix initialize() race condition using tokio::sync::OnceCell (High)
- Fix localhost check bypass via proper URL parsing (High)
- Extract shared stream_transport_send() to deduplicate stdio/unix send logic
- Use atomic write (tmp+rename) for config file persistence
- Filter SSE responses by request_id to prevent wrong-response dispatch
- Share a single reqwest::Client for OAuth via fallible OnceLock
- Log notification send errors instead of silently discarding
- Fix unwrap_or(0) that could steal id=0 responses
- Store InitializeResult in OnceCell so callers can access server capabilities
- Add redirect logging in OAuth discovery
- Reuse is_localhost_url() in auth.rs
- Add McpToolWrapper unit tests and regression tests
- URL-encode PKCE challenge for consistency
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* chore: retrigger CI with skip-regression-check label
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* refactor: encapsulate leaked abstractions from main.rs and app.rs into owning modules
Move module-specific initialization logic out of main.rs (1222→665 lines, -46%) and
app.rs (944→780 lines, -17%) into their respective owning modules as public factory
functions. This enforces separation of concerns so that adding a new DB backend, MCP
transport, or channel doesn't require editing main.rs/app.rs.
Key changes:
- Tracing init functions → src/tracing_fmt.rs
- DB connection factory (connect_with_handles + DatabaseHandles) → src/db/mod.rs
- Secrets store factory (create_secrets_store) → src/secrets/mod.rs
- MCP transport dispatch factory (create_client_from_config) → src/tools/mcp/factory.rs
- Orchestrator setup (setup_orchestrator + OrchestratorSetup) → src/orchestrator/mod.rs
- WASM channel setup (setup_wasm_channels) → src/channels/wasm/setup.rs
- Worker entry points (run_worker, run_claude_bridge) → src/worker/mod.rs
- Shared CLI secrets init (init_secrets_store) → src/cli/mod.rs
- Tunnel startup (start_managed_tunnel) → src/tunnel/mod.rs
- Onboard check (check_onboard_needed) → src/setup/mod.rs
- ExtensionManager unified MCP: uses create_client_from_config via McpProcessManager,
enabling stdio/Unix transports for hot-activated MCP servers
- Deduplicated ~130 lines of secrets store init across cli/mcp.rs and cli/tool.rs
- CLAUDE.md updated with module-owned initialization guideline
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* refactor: address review feedback — deduplicate db factory, extract channel helper
- connect_from_config() now delegates to connect_with_handles() to eliminate
duplicated backend-matching logic (Copilot review feedback)
- Extract register_channel() helper from setup_wasm_channels() loop body
to improve readability (Gemini review feedback)
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* style: fix rustfmt line wrapping in setup_wasm_channels
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* test: add integration test for module-owned initialization factories
Exercises the full factory chain end-to-end to verify nothing was lost
when initialization logic was moved from main.rs/app.rs into owning modules:
- connect_with_handles returns Database + populated backend handles
- connect_from_config delegates correctly (produces working Database)
- secrets::create_secrets_store builds working store from DatabaseHandles
- db::create_secrets_store standalone factory round-trips secrets
- Both secrets factories produce compatible stores (cross-read works)
- ExtensionManager constructs with McpProcessManager and is functional
- DatabaseHandles default is empty
All tests run without external services using libsql in-memory/tempfile.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: wire cli/mcp.rs and cli/tool.rs to shared init_secrets_store()
Both files had inline implementations identical to cli::init_secrets_store().
Replace with delegation to complete the claimed deduplication.
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* style: fix rustfmt line wrapping in integration test
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix(review): remove unused Config import and deduplicate Error Handling section
- Remove `#[allow(unused_imports)]` and unused `use crate::config::Config`
from cli/tool.rs (no longer needed after delegating to shared
`cli::init_secrets_store()`)
- Remove duplicate Error Handling subsection from CLAUDE.md Key Patterns
(all four bullets already exist in Code Style section and
review-discipline.md)
Addresses Copilot review comments.
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix(review): address remaining Copilot review comments
- secrets/mod.rs: clarify docstring that None is a normal no-db condition
- app.rs: add comment explaining the empty_handles fallback path
- orchestrator/mod.rs: combine duplicated sandbox condition into single block
- setup/mod.rs: document env var reads and thread-safety caveat
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Henry Park <[email protected]>