Skip to content

Conversation

ACakshay
Copy link

@ACakshay ACakshay commented Jun 11, 2025

Summary

added stateless streamable_http endpoint for MCP at /mcp/http

Ref: #1158

List of files changed and why

  • deploy/docker/mcp_bridge.py - To mount the endpoint and app for streamable http as well in attach_mcp function and now returning the lifespan required for the same. Official example
  • deploy/docker/utils.py - added a utility that takes all lifespans and combine it into single lifespan function for fastapi app
  • deploy/docker/server.py - combined and added the lifespan of attach_mcp with existing one
  • tests/mcp/test_mcp_http.py - Added test similar to test_mcp_sse.py for http server
  • docs/md_v2/core/docker-deployment.md - Updated documentation to add url and testing instruction
  • deploy/docker/README.md - Updated documentation to add url and testing instruction

How Has This Been Tested?

Tested all the tools of mcp server via postman and also added tests/mcp/test_mcp_http.py with test to list all tools

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added/updated unit tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Summary by CodeRabbit

  • New Features
    • Added a streamable HTTP endpoint for MCP, allowing interaction with the server via HTTP in addition to existing WebSocket support.
  • Documentation
    • Updated deployment and usage guides to include information and testing instructions for the new MCP HTTP endpoint.
  • Tests
    • Introduced a new test script to verify the functionality of the MCP HTTP endpoint.

Copy link
Contributor

coderabbitai bot commented Jun 11, 2025

"""

Walkthrough

The changes introduce a new streamable HTTP endpoint for the MCP (Model Context Protocol) in the server, update related documentation, add a corresponding test script, and refactor the server's lifespan management to support multiple async context managers. Utility code is added to combine lifespans, and documentation is updated to reflect the new endpoint and testing instructions.

Changes

File(s) Change Summary
deploy/docker/mcp_bridge.py Adds streamable HTTP MCP integration, updates attach_mcp to return a lifespan context manager, minor formatting.
deploy/docker/server.py Refactors to combine multiple lifespans, integrates MCP lifespan, adjusts app instantiation and comments.
deploy/docker/utils.py Adds combine_lifespans utility for merging multiple async lifespan context managers.
docs/md_v2/core/docker-deployment.md, deploy/docker/README.md Updates documentation to describe the new MCP HTTP endpoint and its testing procedure.
tests/mcp/test_mcp_http.py Adds a new async test script for the MCP HTTP endpoint, demonstrating tool listing via streamable HTTP.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant HTTP_Handler as MCP HTTP Handler
    participant SessionManager as StreamableHTTPSessionManager
    participant MCP_Server

    Client->>HTTP_Handler: HTTP request to /mcp/http
    HTTP_Handler->>SessionManager: Delegate request
    SessionManager->>MCP_Server: Process MCP command (e.g., list_tools)
    MCP_Server-->>SessionManager: Return response
    SessionManager-->>HTTP_Handler: Stream response
    HTTP_Handler-->>Client: Stream response back
Loading

Poem

In the warren of code, a new path appears,
HTTP streams for MCP, let’s give three cheers!
Lifespans combined, context flows free,
Docs and tests updated for all to see.
With every new endpoint, our server hops higher—
The future is streamable, and rabbits aspire! 🐇
"""

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
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 (10)
docs/md_v2/core/docker-deployment.md (1)

260-265: Unify endpoint casing to avoid copy-paste errors

Streamable_http is the only entry using a capital “S” and an underscore inside the name. Everywhere else in the codebase the transport is referred to as streamable_http.
Align the casing here to prevent users from hitting a 404 due to a typo.

- - **Streamable_http**: `http://localhost:11235/mcp/http`
+ - **Streamable_http**: `http://localhost:11235/mcp/http`
deploy/docker/README.md (1)

260-265: Endpoint name differs from implementation

Same nit as in the main docs file – please change Streamable_httpstreamable_http (or whichever canonical form you settle on) to keep docs, tests and server logs in sync.

deploy/docker/utils.py (2)

67-68: Swallowed exception hides MX lookup problems

The broad except Exception returns False silently. Logging the exception (at debug/info) helps diagnose DNS or network issues.

-    except Exception as e:
-        return False
+    except Exception as exc:
+        logging.getLogger(__name__).debug("verify_email_domain(%s) failed: %s", email, exc)
+        return False

70-85: combine_lifespans – simpler and safer with AsyncExitStack context manager

Manual __aenter__/__aexit__ is easy to get wrong if an enter_async_context fails early. You can rely on the stack’s own context-manager interface:

-    @contextlib.asynccontextmanager
-    async def combined(app):
-        # Nest the lifespans like contextlib.ExitStack does for async
-        stack = contextlib.AsyncExitStack()
-        await stack.__aenter__()
-
-        try:
-            for lifespan in lifespans:
-                await stack.enter_async_context(lifespan(app))
-            yield
-        finally:
-            await stack.__aexit__(None, None, None)
+    @contextlib.asynccontextmanager
+    async def combined(app):
+        async with contextlib.AsyncExitStack() as stack:
+            for lifespan in lifespans:
+                await stack.enter_async_context(lifespan(app))
+            yield

Shorter and automatically handles partial-enter failure cases.

deploy/docker/server.py (2)

10-15: Remove unused import contextlib

contextlib is imported here but never referenced. Drop it to satisfy Ruff and keep imports tidy.

🧰 Tools
🪛 Ruff (0.11.9)

10-10: contextlib imported but unused

Remove unused import: contextlib

(F401)


601-606: Use a logger instead of print() for server start notice

print() executes at import time, which is undesirable with Gunicorn/Uvicorn workers and pollutes stdout.
Switch to logging.getLogger(__name__).info(...) or remove entirely—Uvicorn already logs startup.

deploy/docker/mcp_bridge.py (4)

6-6: Remove unused import Any.

The static analysis correctly identifies that Any is imported but never used in the code.

-from typing import Any, AsyncContextManager, Callable, Dict, List, Tuple
+from typing import AsyncContextManager, Callable, Dict, List, Tuple
🧰 Tools
🪛 Ruff (0.11.9)

6-6: typing.Any imported but unused

Remove unused import: typing.Any

(F401)


22-22: Remove unused import Mount.

The static analysis correctly identifies that Mount is imported but never used in the code.

 from starlette.applications import Starlette
-from starlette.routing import Mount
 from starlette.types import Receive, Scope, Send
🧰 Tools
🪛 Ruff (0.11.9)

22-22: starlette.routing.Mount imported but unused

Remove unused import: starlette.routing.Mount

(F401)


271-280: Replace print statements with proper logging.

Consider using proper logging instead of print statements for production-ready code. This will provide better control over log levels and formatting.

Add logging import at the top of the file:

import logging

logger = logging.getLogger(__name__)

Then update the lifespan function:

 @contextlib.asynccontextmanager
 async def mcp_lifespan(_: Starlette) -> AsyncIterator[None]:
     """Context manager for session manager."""
     async with session_manager.run():
-        print("Application started with StreamableHTTP session manager!")
+        logger.info("Application started with StreamableHTTP session manager")
         try:
             yield
         finally:
-            print("Application shutting down...")
+            logger.info("Application shutting down")

84-297: Consider decomposing this function for better maintainability.

The static analysis correctly identifies that attach_mcp has grown quite large (29 local variables, 77 statements). While functional, consider breaking it down into smaller, focused functions in a future refactoring:

  • Extract route registration logic
  • Extract MCP handler setup
  • Extract transport configuration (WebSocket, SSE, HTTP)

This would improve readability and testability.

🧰 Tools
🪛 Ruff (0.11.9)

135-135: Loop control variable proxy not used within loop body

Rename unused proxy to _proxy

(B007)

🪛 Pylint (3.3.7)

[refactor] 84-84: Too many local variables (29/15)

(R0914)


[refactor] 84-84: Too many statements (77/50)

(R0915)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b4bb0cc and a1d47d1.

📒 Files selected for processing (6)
  • deploy/docker/README.md (2 hunks)
  • deploy/docker/mcp_bridge.py (8 hunks)
  • deploy/docker/server.py (8 hunks)
  • deploy/docker/utils.py (2 hunks)
  • docs/md_v2/core/docker-deployment.md (2 hunks)
  • tests/mcp/test_mcp_http.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
deploy/docker/utils.py (1)
deploy/docker/server.py (1)
  • lifespan (106-114)
🪛 Ruff (0.11.9)
tests/mcp/test_mcp_http.py

5-6: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)

deploy/docker/mcp_bridge.py

6-6: typing.Any imported but unused

Remove unused import: typing.Any

(F401)


22-22: starlette.routing.Mount imported but unused

Remove unused import: starlette.routing.Mount

(F401)

deploy/docker/server.py

10-10: contextlib imported but unused

Remove unused import: contextlib

(F401)


37-37: utils.FilterType imported but unused

Remove unused import: utils.FilterType

(F401)


43-43: Redefinition of unused List from line 15

Remove definition: List

(F811)


44-44: contextlib._AsyncGeneratorContextManager imported but unused

Remove unused import

(F401)


44-44: contextlib.AsyncExitStack imported but unused

Remove unused import

(F401)

🪛 Pylint (3.3.7)
deploy/docker/mcp_bridge.py

[refactor] 84-84: Too many local variables (29/15)

(R0914)


[refactor] 84-84: Too many statements (77/50)

(R0915)

🔇 Additional comments (3)
deploy/docker/server.py (1)

615-616: Lifespan assignment works – 👍

The new combine_lifespans call correctly merges the app and MCP lifespans and replaces the deprecated lifespan= parameter. Good catch.

deploy/docker/mcp_bridge.py (2)

84-90: Good architectural improvement for lifespan management.

The refactoring to return a callable that produces an async context manager is a clean approach for integrating multiple lifespans in the FastAPI application. This enables proper lifecycle management of the new HTTP transport components.

🧰 Tools
🪛 Pylint (3.3.7)

[refactor] 84-84: Too many local variables (29/15)

(R0914)


[refactor] 84-84: Too many statements (77/50)

(R0915)


259-264:

❓ Verification inconclusive

Verify the event_store=None configuration.

Please confirm that passing event_store=None to the StreamableHTTPSessionManager is the intended behavior for stateless operation. This might affect event persistence or streaming capabilities.


🏁 Script executed:

#!/bin/bash
# Search for StreamableHTTPSessionManager usage patterns in the codebase
ast-grep --pattern 'StreamableHTTPSessionManager($$$)'

# Look for documentation or other usages of event_store parameter
rg -A 5 -B 5 "event_store" --type py

Length of output: 1001


🏁 Script executed:

#!/bin/bash
# Locate where StreamableHTTPSessionManager is defined and inspect its constructor
rg -n "class StreamableHTTPSessionManager" --type py -C5
rg -n "def __init__" "$(rg -l 'class StreamableHTTPSessionManager' --type py)" -C5

Length of output: 185


Verify event_store=None usage
We couldn’t locate the StreamableHTTPSessionManager definition in the repo to confirm the default behavior of event_store=None. Please double-check that passing None here truly disables persistence (as intended for a stateless endpoint) and doesn’t inadvertently break streaming or error handling.

• File: deploy/docker/mcp_bridge.py lines 259–264

Copy link
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: 2

♻️ Duplicate comments (1)
deploy/docker/server.py (1)

37-38: ⚠️ Potential issue

Prune unused symbol FilterType

FilterType isn’t used in this module; leaving it here re-introduces the linter warning flagged in the previous review. While touching the line, also drop the duplicate whitespace after the opening parenthesis.

-from utils import (
-    FilterType, combine_lifespans, load_config, setup_logging, verify_email_domain
+from utils import (
+    combine_lifespans, load_config, setup_logging, verify_email_domain
 )
🧰 Tools
🪛 Ruff (0.11.9)

37-37: utils.FilterType imported but unused

Remove unused import: utils.FilterType

(F401)

🧹 Nitpick comments (4)
deploy/docker/server.py (4)

119-119: Minor nit: stray inline comment formatting

#all is missing a space after the hash. Consider tidying for consistency:

-) #all lifespans will be added just before passing app to asgi server
+)  # All lifespans will be added just before passing app to ASGI server

246-251: Return payload duplicates request parameters

Echoing request parameters back verbatim (url, filter, query, cache) adds bandwidth but no functional value to the client, which already knows what it sent. Unless this is required by a public contract, consider omitting them to keep responses lean.


529-544: Docstring grammar / typos

Spotted a few small issues (“design” → “designed”, “Alway is BEST practice” → “It is always best practice”, “lenght”). Polishing the wording improves API self-documentation.

-    This end point is design for any questions about Crawl4ai library.
+    This endpoint is designed for any questions about the Crawl4AI library.
-    Alway is BEST practice you provide a query to filter the context.
+    It is always best practice to provide a query to filter the context.

564-566: Consistent key ordering

For parity with the non-query path above, consider returning doc_context first or using an OrderedDict / explicit ordering to avoid flaky snapshot tests.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a1d47d1 and 7bdfba1.

📒 Files selected for processing (1)
  • deploy/docker/server.py (8 hunks)
🧰 Additional context used
🪛 Ruff (0.11.9)
deploy/docker/server.py

10-10: contextlib imported but unused

Remove unused import: contextlib

(F401)


37-37: utils.FilterType imported but unused

Remove unused import: utils.FilterType

(F401)

🔇 Additional comments (1)
deploy/docker/server.py (1)

612-615: Verify combine_lifespans behaviour

Assigning app.router.lifespan_context post-instantiation is fine, but double-check that:

  1. combine_lifespans preserves the execution order you expect (original app warm-up first, MCP second or vice-versa).
  2. No other part of the code mutates router.lifespan_context afterwards.

If unsure, add an assertion or unit test around the combined context manager.

Copy link
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: 0

♻️ Duplicate comments (2)
deploy/docker/server.py (2)

35-37: Remove the unused FilterType import (Ruff F401).
The symbol isn’t referenced anywhere in this file and keeps triggering the linter.

-from utils import (
-    FilterType, combine_lifespans, load_config, setup_logging, verify_email_domain
-)
+from utils import (
+    combine_lifespans, load_config, setup_logging, verify_email_domain
+)
🧰 Tools
🪛 Ruff (0.11.9)

36-36: utils.FilterType imported but unused

Remove unused import: utils.FilterType

(F401)


599-604: Swap print for structured logging.
Keeping a raw print alongside a full logging setup produces inconsistent output and ignores log-level configuration.

-print(f"MCP server running on {config['app']['host']}:{config['app']['port']}")
+import logging
+logger = logging.getLogger(__name__)
+logger.info("MCP server running on %s:%s", config['app']['host'], config['app']['port'])
🧹 Nitpick comments (1)
deploy/docker/server.py (1)

528-531: Tighten up doc-string grammar.
Minor wording tweaks improve clarity/readability.

-    This end point is design for any questions about Crawl4ai library. It returns a plain text markdown with extensive information about Crawl4ai.
-    You can use this as a context for any AI assistant. Use this endpoint for AI assistants to retrieve library context for decision making or code generation tasks.
-    Alway is BEST practice you provide a query to filter the context. Otherwise the lenght of the response will be very long.
+    This endpoint is designed for any questions about the Crawl4AI library. It returns Markdown with extensive information about Crawl4AI.
+    AI assistants can use the response as context for decision-making or code-generation tasks.
+    As a best practice, always provide a query to filter the context; otherwise the response may be very long.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7bdfba1 and 2075d68.

📒 Files selected for processing (1)
  • deploy/docker/server.py (7 hunks)
🧰 Additional context used
🪛 Ruff (0.11.9)
deploy/docker/server.py

36-36: utils.FilterType imported but unused

Remove unused import: utils.FilterType

(F401)

🔇 Additional comments (1)
deploy/docker/server.py (1)

611-614: Double-check lifespan registration order.
app.router.lifespan_context is overwritten here; if another module mutates it later, only the last assignment will take effect.
Confirm no subsequent code (or uvicorn import side-effects) replaces this attribute; otherwise wrap all lifespans once and assign a single time.

@ACakshay
Copy link
Author

Hi @aravindkarnam
This is an enhancement for the existing mcp-server, adding support for latest transport method as the existing sse one is deprecated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant