Upload 213 files
Browse files- N8N_ENDPOINTS.md +2 -0
- README.md +11 -0
- app.py +12 -0
- renderer_app.py +1 -0
- requirements.renderer.txt +3 -2
- requirements.txt +3 -3
- tests/test_ui_routing.py +8 -0
N8N_ENDPOINTS.md
CHANGED
|
@@ -866,6 +866,8 @@ These are not business API operations:
|
|
| 866 |
| `/whisper/` | Whisper Gradio interface. |
|
| 867 |
| `/editor/` | FFmpeg editor Gradio interface. |
|
| 868 |
| `/mcp/` | MCP Streamable HTTP transport; use an MCP client rather than a normal REST request. |
|
|
|
|
|
|
|
| 869 |
| `/docs` | Root FastAPI Swagger UI when `ENABLE_API_DOCS=true`. |
|
| 870 |
| `/redoc` | Root FastAPI ReDoc UI when `ENABLE_API_DOCS=true`. |
|
| 871 |
| `/openapi.json` | Root renderer OpenAPI schema when `ENABLE_API_DOCS=true`. |
|
|
|
|
| 866 |
| `/whisper/` | Whisper Gradio interface. |
|
| 867 |
| `/editor/` | FFmpeg editor Gradio interface. |
|
| 868 |
| `/mcp/` | MCP Streamable HTTP transport; use an MCP client rather than a normal REST request. |
|
| 869 |
+
| `/dashboard/gradio_api/mcp/` | Gradio-native MCP transport for documented dashboard actions. |
|
| 870 |
+
| `/mcp-health` | MCP transport discovery and custom tool count. |
|
| 871 |
| `/docs` | Root FastAPI Swagger UI when `ENABLE_API_DOCS=true`. |
|
| 872 |
| `/redoc` | Root FastAPI ReDoc UI when `ENABLE_API_DOCS=true`. |
|
| 873 |
| `/openapi.json` | Root renderer OpenAPI schema when `ENABLE_API_DOCS=true`. |
|
README.md
CHANGED
|
@@ -21,6 +21,7 @@ One Hugging Face Docker Space containing the five original applications.
|
|
| 21 |
| Whisper operator | `/whisper/` |
|
| 22 |
| FFmpeg editor | `/editor/` |
|
| 23 |
| MCP Streamable HTTP | `/mcp/` |
|
|
|
|
| 24 |
|
| 25 |
The rendering API remains available at the Space root. `GET /apps` returns the
|
| 26 |
mounted application paths.
|
|
@@ -43,6 +44,16 @@ Connect an MCP client to:
|
|
| 43 |
https://<space-subdomain>.hf.space/mcp/
|
| 44 |
```
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
Every FastAPI application endpoint is exposed as a separate MCP tool. Tool
|
| 47 |
arguments use a consistent transport wrapper:
|
| 48 |
|
|
|
|
| 21 |
| Whisper operator | `/whisper/` |
|
| 22 |
| FFmpeg editor | `/editor/` |
|
| 23 |
| MCP Streamable HTTP | `/mcp/` |
|
| 24 |
+
| Gradio-native MCP | `/dashboard/gradio_api/mcp/` |
|
| 25 |
|
| 26 |
The rendering API remains available at the Space root. `GET /apps` returns the
|
| 27 |
mounted application paths.
|
|
|
|
| 44 |
https://<space-subdomain>.hf.space/mcp/
|
| 45 |
```
|
| 46 |
|
| 47 |
+
This is the custom MCP server and exposes every FastAPI endpoint. The renderer
|
| 48 |
+
dashboard also exposes its documented Gradio actions through Gradio-native MCP:
|
| 49 |
+
|
| 50 |
+
```text
|
| 51 |
+
https://<space-subdomain>.hf.space/dashboard/gradio_api/mcp/
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
Use `GET /mcp-health` in a browser or HTTP client to verify both advertised
|
| 55 |
+
transport URLs and the generated custom tool count.
|
| 56 |
+
|
| 57 |
Every FastAPI application endpoint is exposed as a separate MCP tool. Tool
|
| 58 |
arguments use a consistent transport wrapper:
|
| 59 |
|
app.py
CHANGED
|
@@ -61,6 +61,7 @@ async def studio_apps():
|
|
| 61 |
"whisper": "/whisper/",
|
| 62 |
"editor": "/editor/",
|
| 63 |
"mcp": "/mcp/",
|
|
|
|
| 64 |
"mcp_tools": len(mcp_tool_catalog),
|
| 65 |
}
|
| 66 |
|
|
@@ -82,6 +83,17 @@ mcp, mcp_tool_catalog = create_mcp_server(
|
|
| 82 |
mcp_http_app = mcp.streamable_http_app()
|
| 83 |
|
| 84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
@asynccontextmanager
|
| 86 |
async def studio_lifespan(root_app):
|
| 87 |
async with AsyncExitStack() as stack:
|
|
|
|
| 61 |
"whisper": "/whisper/",
|
| 62 |
"editor": "/editor/",
|
| 63 |
"mcp": "/mcp/",
|
| 64 |
+
"gradio_mcp": "/dashboard/gradio_api/mcp/",
|
| 65 |
"mcp_tools": len(mcp_tool_catalog),
|
| 66 |
}
|
| 67 |
|
|
|
|
| 83 |
mcp_http_app = mcp.streamable_http_app()
|
| 84 |
|
| 85 |
|
| 86 |
+
@app.get("/mcp-health", include_in_schema=False)
|
| 87 |
+
async def mcp_health():
|
| 88 |
+
return {
|
| 89 |
+
"status": "ok",
|
| 90 |
+
"custom_mcp_url": "/mcp/",
|
| 91 |
+
"gradio_mcp_url": "/dashboard/gradio_api/mcp/",
|
| 92 |
+
"custom_tool_count": len(mcp_tool_catalog),
|
| 93 |
+
"transports": ["streamable-http"],
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
|
| 97 |
@asynccontextmanager
|
| 98 |
async def studio_lifespan(root_app):
|
| 99 |
async with AsyncExitStack() as stack:
|
renderer_app.py
CHANGED
|
@@ -374,6 +374,7 @@ app = gr.mount_gradio_app(
|
|
| 374 |
create_dashboard(),
|
| 375 |
path="/dashboard",
|
| 376 |
root_path="/dashboard",
|
|
|
|
| 377 |
)
|
| 378 |
|
| 379 |
|
|
|
|
| 374 |
create_dashboard(),
|
| 375 |
path="/dashboard",
|
| 376 |
root_path="/dashboard",
|
| 377 |
+
mcp_server=True,
|
| 378 |
)
|
| 379 |
|
| 380 |
|
requirements.renderer.txt
CHANGED
|
@@ -1,8 +1,9 @@
|
|
| 1 |
fastapi>=0.115.0
|
| 2 |
uvicorn[standard]>=0.30.0
|
| 3 |
-
gradio
|
| 4 |
huggingface_hub<1.0
|
| 5 |
-
pydantic>=2.
|
|
|
|
| 6 |
python-multipart>=0.0.9
|
| 7 |
psutil>=5.9.8
|
| 8 |
pytest>=8.2.0
|
|
|
|
| 1 |
fastapi>=0.115.0
|
| 2 |
uvicorn[standard]>=0.30.0
|
| 3 |
+
gradio[mcp]==5.49.1
|
| 4 |
huggingface_hub<1.0
|
| 5 |
+
pydantic>=2.11,<2.12
|
| 6 |
+
mcp==1.10.1
|
| 7 |
python-multipart>=0.0.9
|
| 8 |
psutil>=5.9.8
|
| 9 |
pytest>=8.2.0
|
requirements.txt
CHANGED
|
@@ -10,19 +10,19 @@ google-auth>=2.29.0
|
|
| 10 |
google-auth-httplib2>=0.2.0
|
| 11 |
google-auth-oauthlib>=1.2.0
|
| 12 |
google-generativeai>=0.7.2
|
| 13 |
-
gradio
|
| 14 |
httplib2>=0.22.0
|
| 15 |
huggingface_hub<1.0
|
| 16 |
httpx>=0.27.0
|
| 17 |
imageio>=2.34.0
|
| 18 |
imageio-ffmpeg>=0.4.9
|
| 19 |
moviepy==1.0.3
|
| 20 |
-
mcp
|
| 21 |
numpy>=1.26.0
|
| 22 |
onnxruntime
|
| 23 |
phonemizer
|
| 24 |
psutil>=5.9.8
|
| 25 |
-
pydantic>=2.
|
| 26 |
pytest>=8.2.0
|
| 27 |
pytest-cov>=5.0.0
|
| 28 |
pysrt>=1.1.2
|
|
|
|
| 10 |
google-auth-httplib2>=0.2.0
|
| 11 |
google-auth-oauthlib>=1.2.0
|
| 12 |
google-generativeai>=0.7.2
|
| 13 |
+
gradio[mcp]==5.49.1
|
| 14 |
httplib2>=0.22.0
|
| 15 |
huggingface_hub<1.0
|
| 16 |
httpx>=0.27.0
|
| 17 |
imageio>=2.34.0
|
| 18 |
imageio-ffmpeg>=0.4.9
|
| 19 |
moviepy==1.0.3
|
| 20 |
+
mcp==1.10.1
|
| 21 |
numpy>=1.26.0
|
| 22 |
onnxruntime
|
| 23 |
phonemizer
|
| 24 |
psutil>=5.9.8
|
| 25 |
+
pydantic>=2.11,<2.12
|
| 26 |
pytest>=8.2.0
|
| 27 |
pytest-cov>=5.0.0
|
| 28 |
pysrt>=1.1.2
|
tests/test_ui_routing.py
CHANGED
|
@@ -31,6 +31,14 @@ class UIRoutingTests(unittest.TestCase):
|
|
| 31 |
self.assertEqual(response.status_code, 200)
|
| 32 |
self.assertIn("/dashboard", response.text)
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
if __name__ == "__main__":
|
| 36 |
unittest.main()
|
|
|
|
| 31 |
self.assertEqual(response.status_code, 200)
|
| 32 |
self.assertIn("/dashboard", response.text)
|
| 33 |
|
| 34 |
+
def test_mcp_discovery_reports_both_transports(self) -> None:
|
| 35 |
+
response = self.client.get("/mcp-health")
|
| 36 |
+
self.assertEqual(response.status_code, 200)
|
| 37 |
+
payload = response.json()
|
| 38 |
+
self.assertEqual(payload["custom_mcp_url"], "/mcp/")
|
| 39 |
+
self.assertEqual(payload["gradio_mcp_url"], "/dashboard/gradio_api/mcp/")
|
| 40 |
+
self.assertGreater(payload["custom_tool_count"], 0)
|
| 41 |
+
|
| 42 |
|
| 43 |
if __name__ == "__main__":
|
| 44 |
unittest.main()
|