22LangChain agent that connects to Keycloak-protected MCP server.
33
44This script demonstrates:
5- 1. Getting an OAuth token from Keycloak via client_credentials grant
6- 2. Connecting to the MCP server with Bearer token authentication
7- 3. Using MCP tools through LangChain
5+ 1. Dynamic Client Registration (DCR) with Keycloak
6+ 2. Getting an OAuth token using the registered client
7+ 3. Connecting to the MCP server with Bearer token authentication
8+ 4. Using MCP tools through LangChain
89
910Usage:
1011 python agents/langchainv1_keycloak.py
4243 "https://mcp-gps-key-n7pc5ej-kc.ashymeadow-ae27942e.eastus2.azurecontainerapps.io/realms/mcp"
4344)
4445
45- # Test client credentials (from DCR)
46- CLIENT_ID = os .getenv ("TEST_CLIENT_ID" , "4f061e11-d30c-4978-bb2e-2164ce26cc51" )
47- CLIENT_SECRET = os .getenv ("TEST_CLIENT_SECRET" , "UKZLK1eEga9FfZTFsqTHXK70ubq0PAJu" )
48-
4946# Configure language model based on API_HOST
5047API_HOST = os .getenv ("API_HOST" , "github" )
5148
7471 base_model = ChatOpenAI (model = os .getenv ("OPENAI_MODEL" , "gpt-4o-mini" ))
7572
7673
77- async def get_keycloak_token () -> str :
74+ async def register_client_via_dcr () -> tuple [str , str ]:
75+ """Register a new client dynamically using Keycloak's DCR endpoint."""
76+ dcr_url = f"{ KEYCLOAK_REALM_URL } /clients-registrations/openid-connect"
77+
78+ logger .info ("📝 Registering client via DCR..." )
79+
80+ async with httpx .AsyncClient () as client :
81+ response = await client .post (
82+ dcr_url ,
83+ json = {
84+ "client_name" : f"langchain-agent-{ datetime .now ().strftime ('%Y%m%d-%H%M%S' )} " ,
85+ "grant_types" : ["client_credentials" ],
86+ "token_endpoint_auth_method" : "client_secret_basic" ,
87+ },
88+ headers = {"Content-Type" : "application/json" },
89+ )
90+
91+ if response .status_code not in (200 , 201 ):
92+ raise Exception (f"DCR failed: { response .status_code } - { response .text } " )
93+
94+ data = response .json ()
95+ client_id = data ["client_id" ]
96+ client_secret = data ["client_secret" ]
97+
98+ logger .info (f"✅ Registered client: { client_id [:20 ]} ..." )
99+ return client_id , client_secret
100+
101+
102+ async def get_keycloak_token (client_id : str , client_secret : str ) -> str :
78103 """Get an access token from Keycloak using client_credentials grant."""
79104 token_url = f"{ KEYCLOAK_REALM_URL } /protocol/openid-connect/token"
80105
81- logger .info (f "🔑 Getting access token from Keycloak..." )
106+ logger .info ("🔑 Getting access token from Keycloak..." )
82107
83108 async with httpx .AsyncClient () as client :
84109 response = await client .post (
85110 token_url ,
86111 data = {
87112 "grant_type" : "client_credentials" ,
88- "client_id" : CLIENT_ID ,
89- "client_secret" : CLIENT_SECRET ,
113+ "client_id" : client_id ,
114+ "client_secret" : client_secret ,
90115 },
91116 headers = {"Content-Type" : "application/x-www-form-urlencoded" },
92117 )
@@ -106,8 +131,9 @@ async def run_agent() -> None:
106131 """
107132 Run the agent to process expense-related queries using authenticated MCP tools.
108133 """
109- # First, get OAuth token from Keycloak
110- access_token = await get_keycloak_token ()
134+ # Register client via DCR and get token
135+ client_id , client_secret = await register_client_via_dcr ()
136+ access_token = await get_keycloak_token (client_id , client_secret )
111137
112138 logger .info (f"📡 Connecting to MCP server: { MCP_SERVER_URL } " )
113139
@@ -155,11 +181,11 @@ async def main():
155181 print ("=" * 60 )
156182 print ("LangChain Agent with Keycloak-Protected MCP Server" )
157183 print ("=" * 60 )
158- print (f "\n Configuration:" )
184+ print ("\n Configuration:" )
159185 print (f" MCP Server: { MCP_SERVER_URL } " )
160186 print (f" Keycloak: { KEYCLOAK_REALM_URL } " )
161- print (f" Client ID: { CLIENT_ID [:20 ]} ..." )
162187 print (f" LLM Host: { API_HOST } " )
188+ print (" Auth: Dynamic Client Registration (DCR)" )
163189 print ()
164190
165191 await run_agent ()
0 commit comments