Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 .
Expand All @@ -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.




3 changes: 3 additions & 0 deletions example/run_server.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
14 changes: 14 additions & 0 deletions example/web-based/fast_api_server.py
Original file line number Diff line number Diff line change
@@ -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)
25 changes: 25 additions & 0 deletions example/web-based/run_mcp_with_flask_call.py
Original file line number Diff line number Diff line change
@@ -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)
13 changes: 7 additions & 6 deletions pymcp/server.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncio
import json
import logging
from uuid import uuid4

from fastapi import FastAPI, Request
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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 => body: {data} query: {session_id}")
rpc_id = data.get("id")
method = data.get("method")
queue = sessions[session_id]["queue"]
Expand Down Expand Up @@ -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")
Expand Down