diff --git a/README.md b/README.md index 5ac5b55..15030a3 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,15 @@ This project was created after encountering several issues(I faced it while tryi ## Usage +To see detailed debug logs from the framework, configure logging in your application before importing or running the server: + +```python +import logging +logging.basicConfig(level=logging.DEBUG) +``` + + + Run the example server: ```bash pip install . @@ -18,8 +27,63 @@ Run the example server: **Note:** Targeting Cursor as a client only. +## Writing Your Own Tool + +To create a tool with this framework, use the `@tool_registry.register` decorator. For example: + +```python +from pymcp.registry import tool_registry + +@tool_registry.register +def my_tool(a: int, b: int) -> str: + """Adds two numbers and returns the result as a string.""" + return f"Result: {a + b}" +``` + +## Hosting the Server with FastAPI and Uvicorn + +The framework provides a FastAPI app instance (`pymcp.server.app`). You can use this app to run your server with Uvicorn: + +```python +from pymcp.server import app +import uvicorn + +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=8088) +``` + +## Configuring Middleware + +You can add custom middleware to the FastAPI app provided by the framework. For example, to add a custom middleware: + +```python +from pymcp.server import app +from fastapi import Request + +@app.middleware("http") +async def custom_middleware(request: Request, call_next): + # Custom logic before request + response = await call_next(request) + # Custom logic after request + return response +``` + +You can add this before running the server with Uvicorn as shown above. Request flow of an example mcp server ![mcp](./mcp.png) +## Logging + +By default, the framework does not configure logging. To see debug or info logs from the framework, configure logging in your application (before importing or running the server): + +```python +import logging +logging.basicConfig(level=logging.DEBUG) # or INFO, WARNING, etc. +``` + +This allows you to control the verbosity and destination of log messages from both your code and the framework. + + + diff --git a/example/run_server.py b/example/run_server.py index e56f4ff..7437e37 100644 --- a/example/run_server.py +++ b/example/run_server.py @@ -1,6 +1,9 @@ +import logging + from pymcp.registry import tool_registry from pymcp.server import app +logging.basicConfig(level=logging.DEBUG) @tool_registry.register def addNumbersTool(a: float, b: float) -> str: diff --git a/example/web-based/fast_api_server.py b/example/web-based/fast_api_server.py new file mode 100644 index 0000000..66afda0 --- /dev/null +++ b/example/web-based/fast_api_server.py @@ -0,0 +1,14 @@ +import uvicorn +from fastapi import FastAPI +from fastapi.responses import JSONResponse + +app = FastAPI() + + +@app.get("/hello") +async def hello(): + return JSONResponse({"message": "Hello from FastAPI!"}) + + +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=5005) diff --git a/example/web-based/run_mcp_with_flask_call.py b/example/web-based/run_mcp_with_flask_call.py new file mode 100644 index 0000000..5d83b54 --- /dev/null +++ b/example/web-based/run_mcp_with_flask_call.py @@ -0,0 +1,25 @@ +import requests + +from pymcp.registry import tool_registry +from pymcp.server import app + + +@tool_registry.register +def callFlaskHelloTool() -> str: + """Calls the /hello endpoint of the Flask API server running on port 5005 and returns the message.""" + try: + resp = requests.get("http://127.0.0.1:5005/hello", timeout=2) + resp.raise_for_status() + data = resp.json() + return f"Flask API says: {data.get('message', 'No message')}" + except Exception as e: + return f"Error calling Flask API: {e}" + + +if __name__ == "__main__": + import uvicorn + + print( + "[INFO] Make sure to start the Flask API server (flask_api_server.py) before running this example." + ) + uvicorn.run(app, host="0.0.0.0", port=8088) diff --git a/pymcp/server.py b/pymcp/server.py index e9320ed..1d2af27 100644 --- a/pymcp/server.py +++ b/pymcp/server.py @@ -1,5 +1,6 @@ import asyncio import json +import logging from uuid import uuid4 from fastapi import FastAPI, Request @@ -33,8 +34,8 @@ async def root(): @app.get("/sse-cursor") async def sse_cursor(request: Request): session_id = str(uuid4()) - print("[MCP] SSE => /sse-cursor connected") - print(f"[MCP] Created sessionId: {session_id}") + logging.debug("[MCP] SSE => /sse-cursor connected") + logging.debug(f"[MCP] Created sessionId: {session_id}") queue = asyncio.Queue() sessions = get_sessions(app) sessions[session_id] = {"initialized": False, "queue": queue} @@ -63,7 +64,7 @@ async def message(request: Request): status_code=404, content={"error": "Invalid or missing sessionId"} ) data = await request.json() - print(f"[MCP] POST /message => body: {data} query: {session_id}") + logging.debug(f"[MCP] POST /message => method: {data.get('method', 'unknown')} query: {session_id}") rpc_id = data.get("id") method = data.get("method") queue = sessions[session_id]["queue"] @@ -149,9 +150,9 @@ async def handle_rpc_method(method, data, session_id, rpc_id, sessions): "id": rpc_id, "result": {"tools": tools_list, "count": len(tools_list)}, } - print(f"[TOOLS] Sending {len(tools_list)} tools to Cursor") - print(f"[TOOLS] Tool names: {[t['name'] for t in tools_list]}") - print(f"[TOOLS] Tool schemas: {json.dumps(tools_list, indent=2)}") + logging.debug(f"[TOOLS] Sending {len(tools_list)} tools to Cursor") + logging.debug(f"[TOOLS] Tool names: {[t['name'] for t in tools_list]}") + logging.debug(f"[TOOLS] Tool schemas: {json.dumps(tools_list, indent=2)}") await queue.put(json.dumps(result)) elif method == "tools/call": tool_name = data.get("params", {}).get("name")