From e6691278ed02fbbd6fb725d7485ddcba7ddc83ad Mon Sep 17 00:00:00 2001 From: HimanM <67066047+HimanM@users.noreply.github.com> Date: Mon, 22 Jun 2026 04:30:09 +0530 Subject: [PATCH] Add MCP debug route for demos --- .gitignore | 2 + backend/main.py | 2 + backend/routes/mcp_debug.py | 61 +++++++++++++++++++ backend/tests/test_mcp_debug_route.py | 20 ++++++ .../07-reliability-and-debug-surface.md | 33 +++++++--- 5 files changed, 108 insertions(+), 10 deletions(-) create mode 100644 backend/routes/mcp_debug.py create mode 100644 backend/tests/test_mcp_debug_route.py diff --git a/.gitignore b/.gitignore index a7d484c..1373981 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ node_modules/ .venv/ venv/ dist/ +/.tmp-playwright +*.err \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index 4aa6869..37ee441 100644 --- a/backend/main.py +++ b/backend/main.py @@ -11,6 +11,7 @@ from routes.cart import router as cart_router from routes.chat import router as chat_router from routes.image_proxy import router as image_router +from routes.mcp_debug import router as mcp_debug_router from routes.meta import router as meta_router from routes.ws import router as ws_router @@ -47,6 +48,7 @@ async def lifespan(app: FastAPI): app.include_router(ws_router) app.include_router(image_router) app.include_router(meta_router) +app.include_router(mcp_debug_router) @app.get('/health') diff --git a/backend/routes/mcp_debug.py b/backend/routes/mcp_debug.py new file mode 100644 index 0000000..d620a53 --- /dev/null +++ b/backend/routes/mcp_debug.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +import json + +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel, Field + +from mcp.client import mcp_client + +router = APIRouter(prefix="/api/mcp", tags=["mcp-debug"]) + + +class MCPDebugRequest(BaseModel): + tool: str = Field(min_length=1) + params: dict = Field(default_factory=dict) + + +def _validate_debug_tool_name(name: str) -> str: + clean = name.strip() + if not clean.startswith("kapruka_"): + raise HTTPException(status_code=400, detail="Only kapruka_* tools are allowed") + return clean + + +def _validate_debug_params(params: dict) -> dict: + try: + payload = json.dumps(params) + except TypeError as exc: + raise HTTPException(status_code=400, detail=f"Params must be JSON-serializable: {exc}") from exc + if len(payload) > 5000: + raise HTTPException(status_code=400, detail="Params payload is too large") + return params + + +@router.get("/debug") +async def mcp_debug_meta(): + try: + tools = await mcp_client.list_tools() + return { + "connected": True, + "tool_count": len(tools), + "tools": [tool.get("name", "") for tool in tools], + } + except Exception as exc: + return { + "connected": False, + "tool_count": 0, + "tools": [], + "error": str(exc), + } + + +@router.post("/debug") +async def mcp_debug_call(req: MCPDebugRequest): + tool = _validate_debug_tool_name(req.tool) + params = _validate_debug_params(req.params) + result = await mcp_client.call_tool(tool, params) + return { + "tool": tool, + "result": result, + } diff --git a/backend/tests/test_mcp_debug_route.py b/backend/tests/test_mcp_debug_route.py new file mode 100644 index 0000000..4d7d7fe --- /dev/null +++ b/backend/tests/test_mcp_debug_route.py @@ -0,0 +1,20 @@ +import unittest +from fastapi import HTTPException + +from routes.mcp_debug import _validate_debug_params, _validate_debug_tool_name + + +class MCPDebugRouteTest(unittest.TestCase): + def test_allows_only_kapruka_tools(self): + self.assertEqual(_validate_debug_tool_name("kapruka_search_products"), "kapruka_search_products") + with self.assertRaises(HTTPException): + _validate_debug_tool_name("search_products") + + def test_rejects_oversized_payloads(self): + self.assertEqual(_validate_debug_params({"q": "cake"}), {"q": "cake"}) + with self.assertRaises(HTTPException): + _validate_debug_params({"blob": "x" * 6000}) + + +if __name__ == "__main__": + unittest.main() diff --git a/docs/feature-checklists/07-reliability-and-debug-surface.md b/docs/feature-checklists/07-reliability-and-debug-surface.md index ae105c2..d89b439 100644 --- a/docs/feature-checklists/07-reliability-and-debug-surface.md +++ b/docs/feature-checklists/07-reliability-and-debug-surface.md @@ -6,16 +6,16 @@ Finish the candidate-inspired hardening work: predictable small-model defaults, ## Checklist -- [ ] Review candidate MCP route and rate-limiting/debug patterns -- [ ] Confirm which reliability work is already shipped versus still missing -- [ ] Add or refine a minimal MCP debug route if still valuable -- [ ] Add lightweight request hardening only where it helps the demo app -- [ ] Keep the solution small and avoid unnecessary platform complexity -- [ ] Verify provider fallback behavior against live or simulated failures -- [ ] Run backend tests relevant to provider selection and fallback -- [ ] Run `npm run lint` -- [ ] Run `npm run build` -- [ ] Verify the flow in a live browser session +- [x] Review candidate MCP route and rate-limiting/debug patterns +- [x] Confirm which reliability work is already shipped versus still missing +- [x] Add or refine a minimal MCP debug route if still valuable +- [x] Add lightweight request hardening only where it helps the demo app +- [x] Keep the solution small and avoid unnecessary platform complexity +- [x] Verify provider fallback behavior against live or simulated failures +- [x] Run backend tests relevant to provider selection and fallback +- [x] Run `npm run lint` +- [x] Run `npm run build` +- [x] Verify the flow in a live browser session - [ ] Commit, push branch, and open draft PR ## Notes @@ -23,3 +23,16 @@ Finish the candidate-inspired hardening work: predictable small-model defaults, - Candidate reference: `tmp_compare/Kapruka-Ai-Shopping-Agent/app/api/mcp/route.js` - Candidate reference: `tmp_compare/Kapruka-Ai-Shopping-Agent/lib/rate-limiter.js` - Candidate reference: `tmp_compare/Kapruka-Ai-Shopping-Agent/lib/mcp/client.js` +- Existing provider fallback and small-model defaults were already shipped earlier, so this iteration only adds the missing MCP debug surface. +- The debug surface is intentionally tiny: `GET /api/mcp/debug` for tool visibility and `POST /api/mcp/debug` for explicit `kapruka_*` tool calls only. +- Lightweight hardening added: + - only `kapruka_*` tools are allowed + - params must be JSON-serializable + - params payload is capped at 5 KB +- Verification evidence: + - `python -m unittest tests.test_mcp_debug_route tests.test_provider_selection tests.test_mcp_client tests.test_chat_route` + - `npm.cmd run lint` + - `npm.cmd run build` + - live route checks against local backend: + - `GET /api/mcp/debug` responded successfully + - invalid `POST /api/mcp/debug` with non-`kapruka_*` tool returned `400`