From a15618a80e7647ea22fcadc97d463ddaac8b6f12 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 05:25:49 +0000 Subject: [PATCH] Add HTTP transport mode for MCP server with --http flag Co-authored-by: hlohaus <983577+hlohaus@users.noreply.github.com> --- README.md | 15 ++++- docs/mcp-usage-guide.md | 42 +++++++++++++ etc/testing/test_mcp_http.py | 61 +++++++++++++++++++ g4f/cli/__init__.py | 5 +- g4f/mcp/README.md | 39 +++++++++++- g4f/mcp/server.py | 114 +++++++++++++++++++++++++++++++++-- 6 files changed, 266 insertions(+), 10 deletions(-) create mode 100644 etc/testing/test_mcp_http.py diff --git a/README.md b/README.md index 4cb231f3..9b90a6dc 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ python -m g4f.cli gui --port 8080 --debug ### MCP Server GPT4Free now includes a Model Context Protocol (MCP) server that allows AI assistants like Claude to access web search, scraping, and image generation capabilities. -**Starting the MCP server:** +**Starting the MCP server (stdio mode):** ```bash # Using g4f command g4f mcp @@ -216,6 +216,19 @@ g4f mcp python -m g4f.mcp ``` +**Starting the MCP server (HTTP mode):** +```bash +# Start HTTP server on port 8765 +g4f mcp --http --port 8765 + +# Custom host and port +g4f mcp --http --host 127.0.0.1 --port 3000 +``` + +HTTP mode provides: +- `POST http://localhost:8765/mcp` - JSON-RPC endpoint +- `GET http://localhost:8765/health` - Health check + **Configuring with Claude Desktop:** Add to your `claude_desktop_config.json`: diff --git a/docs/mcp-usage-guide.md b/docs/mcp-usage-guide.md index 2cb45060..f2a43f73 100644 --- a/docs/mcp-usage-guide.md +++ b/docs/mcp-usage-guide.md @@ -33,6 +33,8 @@ pip install -e . ### 2. Start the MCP Server +**Stdio Mode (Default):** + ```bash # Using g4f command g4f mcp @@ -49,8 +51,33 @@ The server will: - Write responses to stdout - Write debug/error messages to stderr +**HTTP Mode:** + +```bash +# Start HTTP server on default port 8765 +g4f mcp --http + +# Custom port +g4f mcp --http --port 3000 + +# Custom host and port +g4f mcp --http --host 127.0.0.1 --port 8765 +``` + +The HTTP server provides: +- `POST http://localhost:8765/mcp` - JSON-RPC endpoint +- `GET http://localhost:8765/health` - Health check endpoint + +HTTP mode is useful for: +- Web-based integrations +- Testing with HTTP clients +- Remote access +- Debugging with tools like curl or Postman + ### 3. Test the Server +**Stdio Mode:** + ```bash # Send a test request echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | python -m g4f.mcp @@ -61,6 +88,21 @@ Expected output: {"jsonrpc": "2.0", "id": 1, "result": {"protocolVersion": "2024-11-05", "serverInfo": {...}}} ``` +**HTTP Mode:** + +```bash +# Start server +g4f mcp --http --port 8765 + +# In another terminal, test with curl +curl -X POST http://localhost:8765/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' + +# Health check +curl http://localhost:8765/health +``` + ## Configuration ### Claude Desktop diff --git a/etc/testing/test_mcp_http.py b/etc/testing/test_mcp_http.py new file mode 100644 index 00000000..71349029 --- /dev/null +++ b/etc/testing/test_mcp_http.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +"""Test HTTP MCP server functionality + +This script tests the HTTP transport for the MCP server. +""" + +import asyncio +import json +from g4f.mcp.server import MCPServer, MCPRequest + + +async def test_http_server(): + """Test HTTP server methods""" + server = MCPServer() + + print("Testing HTTP MCP Server Functionality") + print("=" * 70) + + # Test that server can be initialized + print("\n✓ Server initialized successfully") + print(f" Server: {server.server_info['name']}") + print(f" Version: {server.server_info['version']}") + + # Test that run_http method exists + if hasattr(server, 'run_http'): + print("\n✓ HTTP transport method (run_http) available") + print(f" Signature: run_http(host, port)") + else: + print("\n✗ HTTP transport method not found") + return + + # Test request handling (same for both transports) + print("\n✓ Testing request handling...") + + init_request = MCPRequest( + jsonrpc="2.0", + id=1, + method="initialize", + params={} + ) + response = await server.handle_request(init_request) + + if response.result and response.result.get("protocolVersion"): + print(f" Protocol Version: {response.result['protocolVersion']}") + print(" ✓ Request handling works correctly") + + print("\n" + "=" * 70) + print("HTTP MCP Server Tests Passed!") + print("\nTo start HTTP server:") + print(" g4f mcp --http --port 8765") + print("\nHTTP endpoints:") + print(" POST http://localhost:8765/mcp - MCP JSON-RPC endpoint") + print(" GET http://localhost:8765/health - Health check") + print("\nExample HTTP request:") + print(' curl -X POST http://localhost:8765/mcp \\') + print(' -H "Content-Type: application/json" \\') + print(' -d \'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}\'') + + +if __name__ == "__main__": + asyncio.run(test_http_server()) diff --git a/g4f/cli/__init__.py b/g4f/cli/__init__.py index 577e53a8..1e167606 100644 --- a/g4f/cli/__init__.py +++ b/g4f/cli/__init__.py @@ -81,11 +81,14 @@ def run_api_args(args): def get_mcp_parser(): mcp_parser = ArgumentParser(description="Run the MCP (Model Context Protocol) server") mcp_parser.add_argument("--debug", "-d", action="store_true", help="Enable verbose logging.") + mcp_parser.add_argument("--http", action="store_true", help="Use HTTP transport instead of stdio.") + mcp_parser.add_argument("--host", default="0.0.0.0", help="Host to bind HTTP server to (default: 0.0.0.0)") + mcp_parser.add_argument("--port", type=int, default=8765, help="Port to bind HTTP server to (default: 8765)") return mcp_parser def run_mcp_args(args): from ..mcp.server import main as mcp_main - mcp_main() + mcp_main(http=args.http, host=args.host, port=args.port) def main(): parser = argparse.ArgumentParser(description="Run gpt4free", exit_on_error=False) diff --git a/g4f/mcp/README.md b/g4f/mcp/README.md index d370f6a1..1da85e3c 100644 --- a/g4f/mcp/README.md +++ b/g4f/mcp/README.md @@ -22,6 +22,8 @@ pip install -e . ### Running the MCP Server +**Stdio Mode (Default)** + Start the MCP server using: ```bash @@ -36,11 +38,31 @@ g4f mcp The server communicates over stdin/stdout using JSON-RPC 2.0 protocol. +**HTTP Mode** + +Start the MCP server with HTTP transport: + +```bash +g4f mcp --http --port 8765 +``` + +This starts an HTTP server with the following endpoints: +- `POST http://localhost:8765/mcp` - MCP JSON-RPC endpoint +- `GET http://localhost:8765/health` - Health check endpoint + +HTTP mode is useful for: +- Web-based integrations +- Testing with curl or HTTP clients +- Remote access (configure host with `--host`) + +Options: +- `--http`: Enable HTTP transport instead of stdio +- `--host HOST`: Host to bind to (default: 0.0.0.0) +- `--port PORT`: Port to bind to (default: 8765) + ### Configuration for AI Assistants -To use this MCP server with an AI assistant like Claude Desktop, add the following to your MCP configuration: - -**For Claude Desktop** (`claude_desktop_config.json`): +**For Claude Desktop (Stdio)** - `claude_desktop_config.json`: ```json { @@ -53,6 +75,17 @@ To use this MCP server with an AI assistant like Claude Desktop, add the followi } ``` +**For HTTP-based clients**: + +Make POST requests to `http://localhost:8765/mcp` with JSON-RPC payloads. + +Example with curl: +```bash +curl -X POST http://localhost:8765/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' +``` + **For VS Code with Cline**: ```json diff --git a/g4f/mcp/server.py b/g4f/mcp/server.py index 9eea3dbe..16f150ba 100644 --- a/g4f/mcp/server.py +++ b/g4f/mcp/server.py @@ -1,7 +1,8 @@ -"""MCP Server implementation using stdio transport +"""MCP Server implementation with stdio and HTTP transports This module implements a Model Context Protocol (MCP) server that communicates -over standard input/output using JSON-RPC 2.0. The server exposes tools for: +over standard input/output using JSON-RPC 2.0, or via HTTP POST endpoints. +The server exposes tools for: - Web search - Web scraping - Image generation @@ -190,12 +191,115 @@ class MCPServer: except Exception as e: sys.stderr.write(f"Error: {e}\n") sys.stderr.flush() + + async def run_http(self, host: str = "0.0.0.0", port: int = 8765): + """Run the MCP server with HTTP transport + + Args: + host: Host to bind the HTTP server to + port: Port to bind the HTTP server to + """ + try: + from aiohttp import web + except ImportError: + sys.stderr.write("Error: aiohttp is required for HTTP transport\n") + sys.stderr.write("Install it with: pip install aiohttp\n") + sys.exit(1) + + async def handle_mcp_request(request: web.Request) -> web.Response: + """Handle MCP JSON-RPC request over HTTP POST""" + try: + # Parse JSON-RPC request from POST body + request_data = await request.json() + + mcp_request = MCPRequest( + jsonrpc=request_data.get("jsonrpc", "2.0"), + id=request_data.get("id"), + method=request_data.get("method"), + params=request_data.get("params") + ) + + # Handle request + response = await self.handle_request(mcp_request) + + # Build response dict + response_dict = { + "jsonrpc": response.jsonrpc, + "id": response.id + } + if response.result is not None: + response_dict["result"] = response.result + if response.error is not None: + response_dict["error"] = response.error + + return web.json_response(response_dict) + + except json.JSONDecodeError as e: + return web.json_response({ + "jsonrpc": "2.0", + "id": None, + "error": { + "code": -32700, + "message": f"Parse error: {str(e)}" + } + }, status=400) + except Exception as e: + return web.json_response({ + "jsonrpc": "2.0", + "id": None, + "error": { + "code": -32603, + "message": f"Internal error: {str(e)}" + } + }, status=500) + + async def handle_health(request: web.Request) -> web.Response: + """Health check endpoint""" + return web.json_response({ + "status": "ok", + "server": self.server_info + }) + + # Create aiohttp application + app = web.Application() + app.router.add_post('/mcp', handle_mcp_request) + app.router.add_get('/health', handle_health) + + # Start server + sys.stderr.write(f"Starting {self.server_info['name']} v{self.server_info['version']} (HTTP mode)\n") + sys.stderr.write(f"Listening on http://{host}:{port}\n") + sys.stderr.write(f"MCP endpoint: http://{host}:{port}/mcp\n") + sys.stderr.write(f"Health check: http://{host}:{port}/health\n") + sys.stderr.flush() + + runner = web.AppRunner(app) + await runner.setup() + site = web.TCPSite(runner, host, port) + await site.start() + + # Keep server running + try: + await asyncio.Event().wait() + except KeyboardInterrupt: + sys.stderr.write("\nShutting down HTTP server...\n") + sys.stderr.flush() + finally: + await runner.cleanup() -def main(): - """Main entry point for MCP server""" +def main(http: bool = False, host: str = "0.0.0.0", port: int = 8765): + """Main entry point for MCP server + + Args: + http: If True, use HTTP transport instead of stdio + host: Host to bind HTTP server to (only used when http=True) + port: Port to bind HTTP server to (only used when http=True) + """ server = MCPServer() - asyncio.run(server.run()) + if http: + asyncio.run(server.run_http(host, port)) + else: + asyncio.run(server.run()) if __name__ == "__main__":