diff --git a/ai/gen-ai-agents/custom-rag-agent/README.md b/ai/gen-ai-agents/custom-rag-agent/README.md index dc1791f74..9f39acc59 100644 --- a/ai/gen-ai-agents/custom-rag-agent/README.md +++ b/ai/gen-ai-agents/custom-rag-agent/README.md @@ -3,7 +3,9 @@ This repository contains the code for the development of a **custom RAG Agent**, **Author**: L. Saetta -**Last updated**: 11/09/2025 +**Reviewed**: 23.09.2025 + +![UI](images/ui_image.png) ## Design and implementation * The agent is implemented using **LangGraph** diff --git a/ai/gen-ai-agents/custom-rag-agent/llm_with_mcp.py b/ai/gen-ai-agents/custom-rag-agent/llm_with_mcp.py index 9ab8862e9..5a2dd66c6 100644 --- a/ai/gen-ai-agents/custom-rag-agent/llm_with_mcp.py +++ b/ai/gen-ai-agents/custom-rag-agent/llm_with_mcp.py @@ -102,7 +102,7 @@ async def _list_tools(self): Fetch tools from the MCP server using FastMCP. Must be async. """ jwt = self.jwt_supplier() - + logger.info("Listing tools from %s ...", self.mcp_url) # FastMCP requires async context + await for client ops. diff --git a/ai/gen-ai-agents/custom-rag-agent/minimal_mcp_server.py b/ai/gen-ai-agents/custom-rag-agent/minimal_mcp_server.py new file mode 100644 index 000000000..0e020a670 --- /dev/null +++ b/ai/gen-ai-agents/custom-rag-agent/minimal_mcp_server.py @@ -0,0 +1,88 @@ +""" +Minimal-but-solid MCP server with JWT hardening and sane defaults. + +- Safe defaults for transports +""" + +from fastmcp import FastMCP + +# to verify the JWT token +# if you don't need to add security, you can remove this +# in newer version of FastMCP should be replaced with +# from fastmcp.server.auth.providers.jwt import JWTVerifier +from fastmcp.server.auth import BearerAuthProvider + +from config import ( + # first four needed only to manage JWT + ENABLE_JWT_TOKEN, + IAM_BASE_URL, + ISSUER, + AUDIENCE, + TRANSPORT, + # needed only if transport is stremable-http + HOST, + PORT, +) + + +# +# if you don't need to add security, you can remove this part and set +# AUTH = None, simply set ENABLE_JWT_TOKEN = False +# +AUTH = None + +if ENABLE_JWT_TOKEN: + # check that a valid JWT token is provided + AUTH = BearerAuthProvider( + # this is the url to get the public key from IAM + # the PK is used to check the JWT + jwks_uri=f"{IAM_BASE_URL}/admin/v1/SigningCert/jwk", + issuer=ISSUER, + audience=AUDIENCE, + ) + +# +# define the MCP server +# +mcp = FastMCP("MCP server with few lines of code, but secure", auth=AUTH) + + +# +# MCP tools definition +# add and write the code for the tools here +# +@mcp.tool +def say_the_truth(user: str) -> str: + """ + This tool, given the name of the user return one of the secret truths. + + Args: + user: The caller's display name or identifier. + + Returns: + A short truth string. + """ + # here you'll put the code that reads and return the info requested + # mark each tool with the annotation + return f"{user}: Less is more!" + + +# +# Run the MCP server +# +if __name__ == "__main__": + # Validate transport + if TRANSPORT not in {"stdio", "streamable-http"}: + # don't use sse! it is deprecated! + raise RuntimeError(f"Unsupported TRANSPORT: {TRANSPORT}") + + if TRANSPORT == "stdio": + # stdio doesn’t support host/port args + mcp.run(transport=TRANSPORT) + else: + # For http/streamable-http transport, host/port are valid + mcp.run( + transport=TRANSPORT, + host=HOST, + port=PORT, + ) diff --git a/ai/gen-ai-agents/custom-rag-agent/ui_mcp_agent.py b/ai/gen-ai-agents/custom-rag-agent/ui_mcp_agent.py index 072e80d59..dc497726d 100644 --- a/ai/gen-ai-agents/custom-rag-agent/ui_mcp_agent.py +++ b/ai/gen-ai-agents/custom-rag-agent/ui_mcp_agent.py @@ -3,11 +3,15 @@ """ import asyncio +import traceback import streamlit as st from mcp_servers_config import MCP_SERVERS_CONFIG # this one contains the backend and the test code only for console from llm_with_mcp import AgentWithMCP, default_jwt_supplier +from utils import get_console_logger + +logger = get_console_logger() # ---------- Page setup ---------- st.set_page_config(page_title="MCP UI", page_icon="🛠️", layout="wide") @@ -50,6 +54,9 @@ except Exception as e: st.session_state.agent = None st.error(f"Failed to connect: {e}") + logger.error(e) + stack_str = traceback.format_exc() + logger.error(stack_str) # ---------- Chat history (display) ---------- for msg in st.session_state.chat: