chore: Split MCP Server into 2 servers; One for recipe (A2UI over MCP…#879
chore: Split MCP Server into 2 servers; One for recipe (A2UI over MCP…#879sugoi-yuzuru wants to merge 1 commit intomainfrom
Conversation
…) and calculator (MCP apps).
There was a problem hiding this comment.
Code Review
This pull request effectively splits the MCP server into two distinct servers for recipes and the calculator, which clarifies the purpose of each sample. The refactoring is well-structured. I've provided a few suggestions to improve security, robustness, and code quality in the new calculator server implementation. The most critical issue is in samples/agent/mcp/recipe/__main__.py, where a click command is called incorrectly, which will lead to a runtime error. Additionally, I've noted a security concern with the permissive CORS policy in the new calculator server, which should be addressed even in a sample application to avoid promoting insecure practices. Lastly, the repository style guide requires tests for code changes, but no tests have been added for the new server logic.
| import sys | ||
|
|
||
| from server import main | ||
|
|
||
| sys.exit(main()) # type: ignore[call-arg] |
There was a problem hiding this comment.
Calling the click command main() directly will result in a TypeError at runtime because the port and transport arguments are not provided. The type: ignore[call-arg] suppresses the static analysis warning but doesn't fix the underlying issue. click commands are meant to be executed from the command line, where click handles parsing arguments. If you intend for this module to be runnable, you should let click handle the execution.
| import sys | |
| from server import main | |
| sys.exit(main()) # type: ignore[call-arg] | |
| from server import main | |
| if __name__ == "__main__": | |
| main() | |
| allow_origins=["*"], | ||
| allow_methods=["*"], | ||
| allow_headers=["*"], |
There was a problem hiding this comment.
Using a wildcard * for allow_origins is insecure as it allows any origin to make requests to your server. This can lead to Cross-Site Request Forgery (CSRF) attacks. While this might be acceptable for a local demo, it's a dangerous practice that could be copied into production code. It's better to restrict the allowed origins to a specific list or make it configurable via an environment variable. You'll also need to import os at the top of the file.
| allow_origins=["*"], | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| allow_origins=os.getenv("A2UI_DEMO_CORS_ALLOWED_ORIGINS", "http://localhost:5173").split(","), | |
| allow_methods=["*"], | |
| allow_headers=["*"], |
| ] | ||
|
|
||
| @app.read_resource() | ||
| async def read_resource(uri: Any) -> str | bytes: |
There was a problem hiding this comment.
The type hint for uri is Any, which is too generic. The mcp library might provide a more specific type for resource URIs, such as mcp.types.ResourceIdentifier. Using a more specific type would improve type safety and code clarity.
| async def read_resource(uri: Any) -> str | bytes: | |
| async def read_resource(uri: types.ResourceIdentifier) -> str | bytes: |
| if str(uri) == "ui://calculator/app": | ||
| return (pathlib.Path(__file__).parent / "apps" / "calculator.html").read_text() | ||
| raise ValueError(f"Unknown resource: {uri}") |
There was a problem hiding this comment.
The call to .read_text() could raise a FileNotFoundError if calculator.html is missing, which would result in an unhandled exception. It would be more robust to wrap this in a try...except block and raise a ValueError similar to how unknown resources are handled. This provides more consistent error handling.
if str(uri) == "ui://calculator/app":
try:
return (pathlib.Path(__file__).parent / "apps" / "calculator.html").read_text()
except FileNotFoundError:
raise ValueError(f"Resource file not found for uri: {uri}")
raise ValueError(f"Unknown resource: {uri}")| sse = SseServerTransport("/messages/") | ||
|
|
||
| async def handle_sse(request: Request): | ||
| async with sse.connect_sse(request.scope, request.receive, request._send) as streams: # type: ignore[reportPrivateUsage] |
There was a problem hiding this comment.
Accessing the private member request._send is not ideal as it relies on internal implementation details of starlette.requests.Request which could change in future versions, breaking this code. If a public API is available, it should be used instead. If not, consider adding a comment explaining why this private access is necessary.
|
|
||
| import uvicorn | ||
|
|
||
| print(f"Server running at 127.0.0.1:{port} using sse") |
There was a problem hiding this comment.
For consistency with the stdio transport logic and to follow best practices in click-based applications, it's better to use click.echo(..., err=True) for logging informational messages to the console instead of print(). This ensures consistent output handling and allows for easier testing and redirection.
click.echo(f"Server running at 127.0.0.1:{port} using sse", err=True)
…) and calculator (MCP apps).
Description
The split is to make the intent of each server clearly separate for its use case as a sample.
Pre-launch Checklist
If you need help, consider asking for advice on the discussion board.