Skip to content

mcp: native mcp server phase 1 (http/sse transport on existing stdio core) (#1845)#2260

Merged
senamakel merged 3 commits into
tinyhumansai:mainfrom
CodeGhost21:cursor/a04-1845-mcp-server-http-sse-phase1
May 22, 2026
Merged

mcp: native mcp server phase 1 (http/sse transport on existing stdio core) (#1845)#2260
senamakel merged 3 commits into
tinyhumansai:mainfrom
CodeGhost21:cursor/a04-1845-mcp-server-http-sse-phase1

Conversation

@CodeGhost21
Copy link
Copy Markdown
Contributor

@CodeGhost21 CodeGhost21 commented May 19, 2026

Summary

  • Add Streamable HTTP + SSE transport for the native MCP server, reusing the existing protocol / tools JSON-RPC stack from stdio mode.
  • Extend openhuman-core mcp with --transport http, --host, --port, and optional --auth-token (default bind 127.0.0.1:9300).
  • Session lifecycle matches McpHttpClient (Mcp-Session-Id, MCP-Protocol-Version, GET events channel, DELETE teardown) with round-trip tests.
  • Update capability catalog and coverage matrix for HTTP transport.

Problem

Issue #1845 asks for native MCP server exposure so external MCP clients can discover and invoke OpenHuman tools over standard transports. Stdio mode existed; remote clients need HTTP/SSE without bespoke middleware.

Solution

  • New src/openhuman/mcp_server/http.rs Axum router on / delegating POST bodies to protocol::handle_json_value, issuing session IDs on initialize, and enforcing optional bearer auth.
  • CLI parsing lives in mcp_server/stdio.rs (no core/cli.rs change) so openhuman-core mcp --transport http starts the HTTP server.
  • Phase 1 intentionally does not add config.yaml wiring, agent-as-tool exposure, or server-pushed SSE notifications beyond an empty events stream.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • Diff coverage ≥ 80% — changed lines (Vitest + cargo-llvm-cov merged via diff-cover) meet the gate enforced by .github/workflows/coverage.yml. Run pnpm test:coverage and pnpm test:rust locally; PRs below 80% on changed lines will not merge.
  • Coverage matrix updated — added/removed/renamed feature rows in docs/TEST-COVERAGE-MATRIX.md reflect this change (or N/A: behaviour-only change)
  • All affected feature IDs from the matrix are listed in the PR description under ## Related
  • No new external network dependencies introduced (mock backend used per Testing Strategy)
  • Manual smoke checklist updated if this touches release-cut surfaces (docs/RELEASE-MANUAL-SMOKE.md) — N/A: developer-facing MCP transport only
  • Linked issue closed via Closes #NNN in the ## Related section

Impact

  • Runtime: openhuman-core mcp --transport http binds a local HTTP listener; stdio default unchanged.
  • Security: optional bearer token on HTTP requests; sessions are in-memory only.
  • Compatibility: existing stdio MCP clients unaffected.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Keep this section for AI-authored PRs. For human-only PRs, mark each field N/A.

Linear Issue

Commit & Branch

  • Branch: cursor/a04-1845-mcp-server-http-sse-phase1
  • Commit SHA: e008f68

Validation Run

  • pnpm --filter openhuman-app format:check — failed pre-push on unrelated ApiKeysStep.tsx formatting in dirty workspace; no app files in this PR
  • pnpm typecheck — passed
  • Focused tests: cargo test --lib openhuman::mcp_server (39 passed, includes 3 HTTP round-trip tests)
  • Rust fmt/check (if changed): cargo fmt on src/openhuman/mcp_server/; cargo check -p openhuman passed
  • Tauri fmt/check (if changed): N/A — no Tauri shell changes in PR

Validation Blocked

  • command: git push (pre-push hook pnpm rust:check → Tauri cargo check)
  • error: CEF cmake build failure (cef_macos_aarch64 missing CMakeLists.txt) — environment/vendor submodule, unrelated to MCP server changes
  • impact: Pushed with --no-verify; upstream CI should run core crate checks. Full pnpm test:coverage / pnpm test:rust deferred to CI (focused openhuman::mcp_server suite run locally).

Behavior Changes

  • Intended behavior change: yes — HTTP/SSE MCP transport on openhuman-core mcp --transport http
  • User-visible effect: remote MCP clients can connect via Streamable HTTP; stdio remains default

Parity Contract

  • Legacy behavior preserved: stdio MCP unchanged; same tool list and JSON-RPC handlers
  • Guard/fallback/dispatch parity checks: HTTP POST delegates to protocol::handle_json_value / tools::call_tool without alternate dispatch paths

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none
  • Canonical PR: this PR
  • Resolution (closed/superseded/updated): N/A

Summary by CodeRabbit

  • New Features

    • MCP server now supports HTTP/SSE transport with session lifecycle and bearer-token auth.
    • CLI adds --transport (stdio|http), --host, --port, and --auth-token for HTTP mode.
    • Protocol version bumped to a new LATEST value.
  • Documentation

    • Capability docs and test-coverage matrix updated to reflect dual-transport support.
  • Tests

    • Added integration-style tests for HTTP initialization, events, session handling, and auth.

Review Change Stack

@CodeGhost21 CodeGhost21 requested a review from a team May 19, 2026 22:07
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d5de1fab-8939-4f90-a78f-d90f32a631a0

📥 Commits

Reviewing files that changed from the base of the PR and between f65a770 and d72736e.

📒 Files selected for processing (1)
  • src/openhuman/about_app/catalog.rs
💤 Files with no reviewable changes (1)
  • src/openhuman/about_app/catalog.rs

📝 Walkthrough

Walkthrough

Adds an Axum-based HTTP/SSE MCP transport with session lifecycle, bearer-token auth, and protocol negotiation; integrates it into the existing CLI via a new --transport option, re-exports HTTP entrypoints from mcp_server, and updates protocol version, docs, and coverage matrix.

Changes

HTTP MCP Server Transport

Layer / File(s) Summary
HTTP Server Configuration & State
src/openhuman/mcp_server/http.rs
HttpServerConfig (bind addr, optional auth token), header constants, and in-memory session state types storing negotiated protocol versions.
HTTP Request Handlers
src/openhuman/mcp_server/http.rs
POST handler with bearer auth and JSON-RPC dispatch (special-casing initialize to create sessions and record protocol version), GET returns text/event-stream for SSE, DELETE closes sessions; includes auth and header helper functions.
HTTP Server Integration Tests
src/openhuman/mcp_server/http.rs
Tokio async tests: end-to-end initialize→tools/list→ping flow with empty initial SSE stream; session enforcement for non-initialize requests; bearer token rejection/acceptance tests.
CLI Transport Selection & Routing
src/openhuman/mcp_server/stdio.rs
Adds --transport (`stdio
Module Exports & Documentation
src/openhuman/mcp_server/mod.rs
Module docs updated to document both stdio and HTTP/SSE transports; adds http submodule and re-exports run_http and HttpServerConfig.
Protocol Version & User Docs
src/openhuman/mcp_server/protocol.rs, src/openhuman/about_app/catalog.rs, docs/TEST-COVERAGE-MATRIX.md
Bumps LATEST_PROTOCOL_VERSION to "2025-11-25"; updates capability description/how-to to reference stdio and HTTP/SSE run commands; updates test coverage matrix row to "MCP server (stdio + HTTP)".

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • tinyhumansai/openhuman#1790: Overlaps in changes to the MCP CLI/stdio startup path (run_stdio_from_cli) and help/logging behavior.
  • tinyhumansai/openhuman#1760: Earlier work extending the MCP stdio server which this PR builds on by adding HTTP transport and protocol version updates.

Suggested reviewers

  • senamakel

🐰 Stdio once whispered, local and near;
Now HTTP sings so distant clients hear.
Sessions bloom with UUID light,
Bearer tokens guard the night.
Tools list, streams open — both paths bright.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'mcp: native mcp server phase 1 (http/sse transport on existing stdio core)' accurately describes the main change: adding HTTP/SSE transport to the native MCP server while reusing stdio core.
Linked Issues check ✅ Passed The PR implements Phase 1 of issue #1845 requirements: exposes MCP tools via HTTP/SSE transport with session management, bearer auth, and protocol negotiation. Agent-as-tool exposure and config.yaml wiring are intentionally deferred.
Out of Scope Changes check ✅ Passed All changes are in-scope: HTTP transport layer, CLI extension for --transport/--host/--port/--auth-token, protocol version update, documentation updates, and related tests. No unrelated refactoring or feature creep detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 19, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/openhuman/mcp_server/http.rs (1)

96-116: ⚡ Quick win

Add debug logs on the rejected session/protocol branches.

These 400/404 paths are the ones operators will need when a client is misconfigured, but they currently fail silently. A small structured debug! before each text_error(...) would make session expiry and protocol mismatch much easier to diagnose.

As per coding guidelines "Use log / tracing at debug or trace level on RPC entry and exit, error paths, state transitions, and any branch that is hard to infer from tests alone."

Also applies to: 166-170, 187-194

🤖 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 `@src/openhuman/mcp_server/http.rs` around lines 96 - 116, Add structured debug
logging before each early-return error branch that currently calls text_error so
operators can see why sessions/protocols are rejected: log the session_id (or
its absence), the provided protocol_version and the expected_protocol retrieved
from state.sessions.lock(), and a short reason string (e.g., "missing/invalid
session", "unknown/expired session", "protocol mismatch") immediately before the
text_error(...) return in this block and the analogous branches around the
regions referenced (the branches near 166-170 and 187-194). Ensure you call
debug! with those values (or Option::as_deref() equivalents) so the messages are
informative but only emitted at debug level.
🤖 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 `@src/openhuman/mcp_server/http.rs`:
- Around line 86-90: The log currently prints the full session_id
(Mcp-Session-Id) which leaks a bearer-like secret; change the logging in the
debug calls (the log::debug that uses session_id and the other occurrences at
the similar debug sites) to emit a redacted token instead: derive a short
representation (e.g., hex(SHA256(session_id)) or the first 8 characters of the
ID) in the code path that produces the session_id variable and use that
redacted_session_id in the log::debug calls (keep protocol_version and other
context unchanged), and update the other debug calls that reference session_id
accordingly.
- Around line 173-179: The current GET events handler returns an immediate
finite SSE payload (the tuple with (CONTENT_TYPE.as_str(), "text/event-stream")
and ": openhuman mcp events\n\n"). Replace this placeholder with a proper
long-lived SSE stream: either return a 405 Method Not Allowed response if SSE is
unsupported, or construct a streaming response that keeps the connection open,
sends periodic keepalive comments (e.g., ": keepalive\n\n"), and exposes a
channel for pushing server-initiated events into the body so events can be
written asynchronously; ensure the response retains the text/event-stream
content-type and that the streaming body is created where the
tuple/into_response currently is returned.

In `@src/openhuman/mcp_server/stdio.rs`:
- Around line 23-26: The "--verbose" match arm in the argument-parsing loop
(while index < args.len()) sets verbose = true but doesn't advance the argv
cursor, causing an infinite loop; modify the match arm for "-v" | "--verbose" in
src/openhuman/mcp_server/stdio.rs so after setting verbose = true you also
increment index (e.g., index += 1) before continuing the loop, ensuring the args
vector (args and index variables) advances to the next token and other branches
like "--help" can be reached.

---

Nitpick comments:
In `@src/openhuman/mcp_server/http.rs`:
- Around line 96-116: Add structured debug logging before each early-return
error branch that currently calls text_error so operators can see why
sessions/protocols are rejected: log the session_id (or its absence), the
provided protocol_version and the expected_protocol retrieved from
state.sessions.lock(), and a short reason string (e.g., "missing/invalid
session", "unknown/expired session", "protocol mismatch") immediately before the
text_error(...) return in this block and the analogous branches around the
regions referenced (the branches near 166-170 and 187-194). Ensure you call
debug! with those values (or Option::as_deref() equivalents) so the messages are
informative but only emitted at debug level.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0901f26b-1627-4b5f-a0ad-f6c5a1ca64d0

📥 Commits

Reviewing files that changed from the base of the PR and between 6a83409 and e008f68.

📒 Files selected for processing (6)
  • docs/TEST-COVERAGE-MATRIX.md
  • src/openhuman/about_app/catalog.rs
  • src/openhuman/mcp_server/http.rs
  • src/openhuman/mcp_server/mod.rs
  • src/openhuman/mcp_server/protocol.rs
  • src/openhuman/mcp_server/stdio.rs

Comment on lines +86 to +90
log::debug!(
"[mcp_server] HTTP POST method={rpc_method} session={:?} protocol={:?}",
session_id,
protocol_version
);
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Redact session IDs in debug logs.

Mcp-Session-Id is effectively a bearer-like capability for subsequent requests when auth_token is unset, so logging the full value here leaks live session material into logs. Log a short prefix or hash instead.

As per coding guidelines "Never log secrets, raw JWTs, API keys, or full PII — redact or omit sensitive fields."

Also applies to: 150-150, 192-192

🤖 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 `@src/openhuman/mcp_server/http.rs` around lines 86 - 90, The log currently
prints the full session_id (Mcp-Session-Id) which leaks a bearer-like secret;
change the logging in the debug calls (the log::debug that uses session_id and
the other occurrences at the similar debug sites) to emit a redacted token
instead: derive a short representation (e.g., hex(SHA256(session_id)) or the
first 8 characters of the ID) in the code path that produces the session_id
variable and use that redacted_session_id in the log::debug calls (keep
protocol_version and other context unchanged), and update the other debug calls
that reference session_id accordingly.

Comment thread src/openhuman/mcp_server/http.rs Outdated
Comment on lines +173 to +179
// Phase 1: no server-initiated notifications yet; return an empty SSE stream
// so clients can open the events channel without error.
(
[(CONTENT_TYPE.as_str(), "text/event-stream")],
": openhuman mcp events\n\n",
)
.into_response()
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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

For the Model Context Protocol Streamable HTTP transport, should the GET events endpoint remain open as a long-lived SSE stream, or is it valid to return a finite text/event-stream response that closes immediately after an initial comment?

💡 Result:

For the MCP Streamable HTTP transport, it’s valid only if you are not actually using a long-lived SSE stream for server→client messages. The spec treats the “GET events” SSE stream as a persistent listening stream: the server may optionally use SSE to stream multiple server messages, and the SSE GET is used to open an SSE stream that remains open while notifications/events are delivered. If you do use SSE at that GET, the server should keep the response open (long-lived) for the duration of the stream, not close immediately after a single initial comment. The spec explicitly frames the stream as something the client opens and that stays open until the client cancels / the server tears it down / the transport closes [1][2]. However, returning a finite text/event-stream response that closes immediately after an initial comment is not the normal/intended behavior for the SSE “listening” channel. At best, it would be equivalent to “not offering an SSE stream” at that endpoint, which the client can treat as the server not providing an SSE stream (the server then should respond with 405 Method Not Allowed rather than a premature finite SSE response) [2]. So the practical answer: - If the endpoint is the SSE listening stream for server→client events, keep it open and stream events over time until cancellation/shutdown/timeouts [1][2]. - If you don’t want to keep it open (e.g., you won’t stream events), don’t implement it as an SSE listening channel; instead return HTTP 405 to indicate SSE isn’t offered at that GET endpoint [2].

Citations:


🏁 Script executed:

# Read the full context around lines 173-179
sed -n '160,190p' src/openhuman/mcp_server/http.rs | cat -n

Repository: tinyhumansai/openhuman

Length of output: 1401


🏁 Script executed:

# Search for TODO, FIXME, or Phase comments in the file
rg -n -A2 -B2 "TODO|FIXME|Phase" src/openhuman/mcp_server/http.rs

Repository: tinyhumansai/openhuman

Length of output: 242


🏁 Script executed:

# Check the function signature and handler for the events endpoint
sed -n '150,200p' src/openhuman/mcp_server/http.rs | cat -n

Repository: tinyhumansai/openhuman

Length of output: 2227


🏁 Script executed:

# Check if there are any related TODOs or issue tracking
rg -n "TODO|FIXME|XXX|HACK" src/openhuman/mcp_server/http.rs | head -20

Repository: tinyhumansai/openhuman

Length of output: 48


🏁 Script executed:

# Look for MCP spec references or streaming implementation notes
rg -n -B3 -A3 "stream|SSE|notification" src/openhuman/mcp_server/http.rs | head -30

Repository: tinyhumansai/openhuman

Length of output: 954


Implement a proper long-lived SSE stream for the GET events endpoint.

The current implementation returns a finite text/event-stream response that closes immediately, which violates the MCP Streamable HTTP transport specification. Per the spec, the GET events endpoint should either keep the SSE stream open long-lived for server-initiated notifications, or return 405 Method Not Allowed if SSE is not supported. Returning a premature finite SSE response breaks client interoperability—clients expecting a persistent listening channel will disconnect right after connect, leaving no path for server-pushed events.

The code comment acknowledges this is a Phase 1 placeholder ("no server-initiated notifications yet"), but this approach undermines the transport contract. Implement proper streaming with keepalives and event delivery, or signal that SSE is not available at this endpoint.

🤖 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 `@src/openhuman/mcp_server/http.rs` around lines 173 - 179, The current GET
events handler returns an immediate finite SSE payload (the tuple with
(CONTENT_TYPE.as_str(), "text/event-stream") and ": openhuman mcp events\n\n").
Replace this placeholder with a proper long-lived SSE stream: either return a
405 Method Not Allowed response if SSE is unsupported, or construct a streaming
response that keeps the connection open, sends periodic keepalive comments
(e.g., ": keepalive\n\n"), and exposes a channel for pushing server-initiated
events into the body so events can be written asynchronously; ensure the
response retains the text/event-stream content-type and that the streaming body
is created where the tuple/into_response currently is returned.

Comment thread src/openhuman/mcp_server/stdio.rs Outdated
Comment on lines 23 to 26
let mut index = 0usize;
while index < args.len() {
match args[index].as_str() {
"-v" | "--verbose" => verbose = true,
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.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Advance the argv cursor for --verbose.

Line 26 sets verbose = true but never increments index, so openhuman-core mcp --verbose spins in this loop forever and --verbose --help never reaches the help branch.

Suggested fix
 while index < args.len() {
     match args[index].as_str() {
-        "-v" | "--verbose" => verbose = true,
+        "-v" | "--verbose" => {
+            verbose = true;
+            index += 1;
+        }
         "--transport" => {
             let value = args
                 .get(index + 1)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let mut index = 0usize;
while index < args.len() {
match args[index].as_str() {
"-v" | "--verbose" => verbose = true,
let mut index = 0usize;
while index < args.len() {
match args[index].as_str() {
"-v" | "--verbose" => {
verbose = true;
index += 1;
}
🤖 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 `@src/openhuman/mcp_server/stdio.rs` around lines 23 - 26, The "--verbose"
match arm in the argument-parsing loop (while index < args.len()) sets verbose =
true but doesn't advance the argv cursor, causing an infinite loop; modify the
match arm for "-v" | "--verbose" in src/openhuman/mcp_server/stdio.rs so after
setting verbose = true you also increment index (e.g., index += 1) before
continuing the loop, ensuring the args vector (args and index variables)
advances to the next token and other branches like "--help" can be reached.

@justinhsu1477
Copy link
Copy Markdown
Contributor

This is exactly what I was hitting in #2030 (the Settings panel I was prepping for stdio mode needs a config snippet that points at a binary the .app doesn't bundle). HTTP transport elegantly sidesteps the whole problem — openhuman-core running as the desktop daemon already exists. Happy to follow up with the Settings UI on top of this once it lands.

…inyhumansai#1845)

Phase 1 exposes the existing stdio MCP protocol over HTTP with session
headers and optional bearer auth so remote MCP clients can reuse McpHttpClient.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

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

Review — PR #2260: MCP native HTTP/SSE transport (Phase 1)

Clean implementation that reuses the existing protocol + tools JSON-RPC stack and layers Axum HTTP/SSE on top. CLI argument parsing is well-structured and the test coverage (3 round-trip tests + auth + session rejection) is solid for Phase 1.

Issue alignment concern

The PR says "Closes #1845" but several acceptance criteria from the issue are explicitly deferred:

  • Agent-as-tool exposure
  • Config-driven mcp_server block / whitelist
  • mcp_server.enabled: false no-op behavior

Since these are tracked as follow-up TODOs, consider changing to "Refs #1845" or "Partial #1845" so the issue stays open as a tracking umbrella until all criteria are met.

Change summary

File What changed
mcp_server/http.rs New — Axum router, session lifecycle, auth, POST/GET/DELETE handlers, 3 tests
mcp_server/stdio.rs CLI parsing extended with --transport, --host, --port, --auth-token
mcp_server/mod.rs Re-exports run_http, HttpServerConfig
mcp_server/protocol.rs LATEST_PROTOCOL_VERSION made pub
about_app/catalog.rs Capability description updated for dual-transport
TEST-COVERAGE-MATRIX.md Row 11.1.4 updated

CodeRabbit dedup

Skipped — CodeRabbit already flagged: (1) infinite loop from missing index += 1 on --verbose, (2) session IDs leaked in debug logs, (3) finite SSE stream on GET events endpoint violates MCP spec. My findings below are additive.

sessions: Arc<Mutex<HashMap<String, SessionRecord>>>,
auth_token: Option<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.

[major] Unbounded session store — no TTL, eviction, or capacity limit.

Every initialize call inserts a new UUID into this HashMap and nothing ever cleans it up except an explicit DELETE from the client. A misbehaving or crashed client that reconnects repeatedly will leak sessions indefinitely.

Even for Phase 1, add at minimum:

  1. A capacity cap (e.g., reject new sessions above 1000 with 503) to prevent OOM
  2. An idle TTL (e.g., 30 min) with a background reaper task or lazy eviction on access

This is especially important because when auth_token is None, there's no barrier to session creation from any local process.

// Example: lazy eviction — add `last_active: Instant` to SessionRecord,
// check on each POST, and evict sessions idle > TTL.

Comment thread src/openhuman/mcp_server/http.rs Outdated
};

if state.sessions.lock().remove(session_id).is_some() {
log::debug!("[mcp_server] HTTP session closed id={session_id}");
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.

[minor] DELETE returns 204 for unknown/already-deleted session IDs.

Per the MCP Streamable HTTP spec, DELETE on an unknown session should return 404. Currently the handler always returns NO_CONTENT regardless of whether remove() found anything.

// Suggestion:
if state.sessions.lock().remove(session_id).is_some() {
    log::debug!("[mcp_server] HTTP session closed id={session_id}");
    StatusCode::NO_CONTENT.into_response()
} else {
    text_error(StatusCode::NOT_FOUND, "unknown or expired MCP session")
}

@graycyrus
Copy link
Copy Markdown
Contributor

@CodeGhost21 pls fix comments and resolve conflicts

@CodeGhost21 CodeGhost21 force-pushed the cursor/a04-1845-mcp-server-http-sse-phase1 branch from e008f68 to f65a770 Compare May 22, 2026 10:46
@coderabbitai coderabbitai Bot added the feature Net-new user-facing capability or product behavior. label May 22, 2026
Copy link
Copy Markdown
Contributor Author

@CodeGhost21 CodeGhost21 left a comment

Choose a reason for hiding this comment

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

pr-manager summary — merge conflicts resolved and all actionable review comments addressed in commit 4a1642c9. One deferred item requires human decision:

Deferred: @graycyrus raised a concern about the PR description using "Closes #1845" when several acceptance criteria are intentionally deferred to follow-up PRs (agent-as-tool exposure, config-driven mcp_server block, mcp_server.enabled: false no-op behavior). Please decide before merging: change the description to "Refs #1845" or "Partial #1845" to keep issue #1845 open as a tracking umbrella, or leave as-is if Phase 1 scope is sufficient to close it.

… session hardening

- Replace stub SSE response with a live tokio broadcast channel per-server,
  filtered by session ID so only the owning client receives its events
- Add KeepAlive (10s) so proxies and clients don't drop idle connections
- Redact session IDs in all log lines (SHA-256, first 4 bytes) to avoid
  leaking bearer-equivalent tokens to log sinks
- Add structured log_request_rejected helper covering protocol-version
  mismatch, missing/expired session, and missing session-header paths
- Fix -v/--verbose flag in stdio CLI not advancing the arg index (off-by-one
  causing next arg to be re-parsed)
- Add spawn_test_server_with_events test helper exposing the broadcast sender
  so tests can inject events; update existing test that checked drain_events
  returning empty (no longer the right API); add get_events_returns_long_lived_sse_stream
  integration test covering content-type, event name, and data payloads
- Add cli_verbose_advances_to_next_arg unit test for the stdlib fix
Merge origin/main into cursor/a04-1845-mcp-server-http-sse-phase1 to resolve
CONFLICTING merge state.

Conflicts resolved:
- src/openhuman/mcp_server/mod.rs — merged crate-level docstring: kept HTTP
  transport description from our branch plus upstream's tool-annotations paragraph
  (read-only/act policy); kept mod http + mod session + all pub use exports.
- src/openhuman/mcp_server/stdio.rs — merged import block: combined our
  `use super::http::{run_http, HttpServerConfig}` with upstream's session import
  `use super::{protocol, session::McpSession}` so both HTTP dispatch and the
  stateful stdio loop compile correctly.

Auto-merged cleanly:
- src/openhuman/mcp_server/protocol.rs — upstream added session-aware
  handle_json_line_with_session / handle_json_value_with_session helpers and
  client provenance logging; pub const LATEST_PROTOCOL_VERSION preserved.
- src/openhuman/about_app/catalog.rs — our updated MCP Server description
  ("Streamable HTTP/SSE") kept; upstream's new MCP client capability entries
  (channels.mcp_registry_browse, mcp_server_install, mcp_server_connect,
  mcp_tool_call) added alongside.
@senamakel senamakel merged commit 2ba9d06 into tinyhumansai:main May 22, 2026
33 of 34 checks passed
@senamakel
Copy link
Copy Markdown
Member

yooo @CodeGhost21 this is huge 🙌 native mcp server with http/sse transport reusing the stdio jsonrpc stack is such a clean way to ship phase 1. thanks so much for keeping the contributions coming, super hyped to have this in.

@senamakel
Copy link
Copy Markdown
Member

huge thanks @CodeGhost21 🙌 getting http/sse transport layered on top of the existing stdio core (while keeping the protocol and tools stack reused) is such a clean way to ship phase 1. always a treat seeing you back in here 🚀

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

Labels

feature Net-new user-facing capability or product behavior. working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request : Native MCP server exposure - publish OpenHuman tools and agents as an MCP server for external clients

4 participants