Skip to content

Commit 73def6b

Browse files
committed
Integrated user session cookie flow; Developed CLI tool for OAuth flow (cli_auth.py); Updated auth server to validate both JWT and session cookies; Updated agent to support --use-session-cookie flag for user-based authentication; Ran integration test with M2M and user session cookie with no failure or regression;
1 parent f6ab2e1 commit 73def6b

File tree

7 files changed

+858
-149
lines changed

7 files changed

+858
-149
lines changed

agents/agent_w_auth.py

Lines changed: 147 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ def load_env_config() -> Dict[str, Optional[str]]:
8888
'client_id': None,
8989
'client_secret': None,
9090
'region': None,
91-
'user_pool_id': None
91+
'user_pool_id': None,
92+
'domain': None
9293
}
9394

9495
if DOTENV_AVAILABLE:
@@ -113,6 +114,7 @@ def load_env_config() -> Dict[str, Optional[str]]:
113114
env_config['client_secret'] = os.getenv('COGNITO_CLIENT_SECRET')
114115
env_config['region'] = os.getenv('AWS_REGION')
115116
env_config['user_pool_id'] = os.getenv('COGNITO_USER_POOL_ID')
117+
env_config['domain'] = os.getenv('COGNITO_DOMAIN')
116118

117119
return env_config
118120

@@ -141,6 +143,12 @@ def parse_arguments() -> argparse.Namespace:
141143
parser.add_argument('--message', type=str, default='what is the current time in Clarksburg, MD',
142144
help='Message to send to the agent')
143145

146+
# Authentication method arguments
147+
parser.add_argument('--use-session-cookie', action='store_true',
148+
help='Use session cookie authentication instead of M2M')
149+
parser.add_argument('--session-cookie-file', type=str, default='~/.mcp/session_cookie',
150+
help='Path to session cookie file (default: ~/.mcp/session_cookie)')
151+
144152
# Cognito authentication arguments - now optional if available in environment
145153
parser.add_argument('--client-id', type=str, default=env_config['client_id'],
146154
help='Cognito App Client ID (can be set via COGNITO_CLIENT_ID env var)')
@@ -150,24 +158,34 @@ def parse_arguments() -> argparse.Namespace:
150158
help='Cognito User Pool ID (can be set via COGNITO_USER_POOL_ID env var)')
151159
parser.add_argument('--region', type=str, default=env_config['region'],
152160
help='AWS region for Cognito (can be set via AWS_REGION env var)')
161+
parser.add_argument('--domain', type=str, default=env_config['domain'],
162+
help='Cognito custom domain (can be set via COGNITO_DOMAIN env var)')
153163
parser.add_argument('--scopes', type=str, nargs='*', default=None,
154164
help='Optional scopes for the token request')
155165

156166
args = parser.parse_args()
157167

158-
# Validate that required Cognito parameters are available (either from command line or environment)
159-
missing_params = []
160-
if not args.client_id:
161-
missing_params.append('--client-id (or COGNITO_CLIENT_ID env var)')
162-
if not args.client_secret:
163-
missing_params.append('--client-secret (or COGNITO_CLIENT_SECRET env var)')
164-
if not args.user_pool_id:
165-
missing_params.append('--user-pool-id (or COGNITO_USER_POOL_ID env var)')
166-
if not args.region:
167-
missing_params.append('--region (or AWS_REGION env var)')
168-
169-
if missing_params:
170-
parser.error(f"Missing required parameters: {', '.join(missing_params)}")
168+
# Validate authentication parameters based on method
169+
if args.use_session_cookie:
170+
# For session cookie auth, we just need the cookie file
171+
cookie_path = os.path.expanduser(args.session_cookie_file)
172+
if not os.path.exists(cookie_path):
173+
parser.error(f"Session cookie file not found: {cookie_path}\n"
174+
f"Run 'python auth_server/cli_auth.py' to authenticate first")
175+
else:
176+
# For M2M auth, validate Cognito parameters
177+
missing_params = []
178+
if not args.client_id:
179+
missing_params.append('--client-id (or COGNITO_CLIENT_ID env var)')
180+
if not args.client_secret:
181+
missing_params.append('--client-secret (or COGNITO_CLIENT_SECRET env var)')
182+
if not args.user_pool_id:
183+
missing_params.append('--user-pool-id (or COGNITO_USER_POOL_ID env var)')
184+
if not args.region:
185+
missing_params.append('--region (or AWS_REGION env var)')
186+
187+
if missing_params:
188+
parser.error(f"Missing required parameters for M2M authentication: {', '.join(missing_params)}")
171189

172190
return args
173191

@@ -210,21 +228,25 @@ def calculator(expression: str) -> str:
210228

211229
@tool
212230
async def invoke_mcp_tool(mcp_registry_url: str, server_name: str, tool_name: str, arguments: Dict[str, Any],
213-
auth_token: str, user_pool_id: str, client_id: str, region: str) -> str:
231+
auth_token: str = None, user_pool_id: str = None, client_id: str = None, region: str = None,
232+
auth_method: str = "m2m", session_cookie: str = None) -> str:
214233
"""
215234
Invoke a tool on an MCP server using the MCP Registry URL and server name with authentication.
216235
217236
This tool creates an MCP SSE client and calls the specified tool with the provided arguments.
237+
Supports both M2M (JWT) and session cookie authentication.
218238
219239
Args:
220240
mcp_registry_url (str): The URL of the MCP Registry
221241
server_name (str): The name of the MCP server to connect to
222242
tool_name (str): The name of the tool to invoke
223243
arguments (Dict[str, Any]): Dictionary containing the arguments for the tool
224-
auth_token (str): Bearer token for authentication
244+
auth_token (str): Bearer token for authentication (for M2M)
225245
user_pool_id (str): Cognito User Pool ID for X-User-Pool-Id header
226246
client_id (str): Cognito Client ID for X-Client-Id header
227247
region (str): AWS region for X-Region header
248+
auth_method (str): Authentication method ("m2m" or "session_cookie")
249+
session_cookie (str): Session cookie value (for session auth)
228250
229251
Returns:
230252
str: The result of the tool invocation as a string
@@ -246,21 +268,29 @@ async def invoke_mcp_tool(mcp_registry_url: str, server_name: str, tool_name: st
246268
server_url = urljoin(base_url, f"{server_name}/sse")
247269
print(f"Server URL: {server_url}")
248270

249-
# Prepare headers for authentication
271+
# Prepare headers based on authentication method
250272
headers = {
251-
'Authorization': f'Bearer {auth_token}',
252-
'X-User-Pool-Id': user_pool_id,
253-
'X-Client-Id': client_id,
254-
'X-Region': region
273+
'X-User-Pool-Id': user_pool_id or '',
274+
'X-Client-Id': client_id or '',
275+
'X-Region': region or 'us-east-1'
255276
}
256277

257-
# Create redacted headers for logging (mask sensitive information)
258-
redacted_headers = {
259-
'Authorization': f'Bearer {redact_sensitive_value(auth_token)}',
260-
'X-User-Pool-Id': redact_sensitive_value(user_pool_id),
261-
'X-Client-Id': redact_sensitive_value(client_id),
262-
'X-Region': region # Region is not sensitive
263-
}
278+
if auth_method == "session_cookie" and session_cookie:
279+
headers['Cookie'] = f'mcp_gateway_session={session_cookie}'
280+
redacted_headers = {
281+
'Cookie': f'mcp_gateway_session={redact_sensitive_value(session_cookie)}',
282+
'X-User-Pool-Id': redact_sensitive_value(user_pool_id) if user_pool_id else '',
283+
'X-Client-Id': redact_sensitive_value(client_id) if client_id else '',
284+
'X-Region': region or 'us-east-1'
285+
}
286+
else:
287+
headers['Authorization'] = f'Bearer {auth_token}'
288+
redacted_headers = {
289+
'Authorization': f'Bearer {redact_sensitive_value(auth_token)}',
290+
'X-User-Pool-Id': redact_sensitive_value(user_pool_id) if user_pool_id else '',
291+
'X-Client-Id': redact_sensitive_value(client_id) if client_id else '',
292+
'X-Region': region or 'us-east-1'
293+
}
264294

265295
try:
266296
# Create an MCP SSE client and call the tool with authentication headers
@@ -400,7 +430,7 @@ async def main():
400430
"""
401431
Main function that:
402432
1. Parses command line arguments
403-
2. Generates Cognito M2M authentication token
433+
2. Generates Cognito M2M authentication token OR loads session cookie
404434
3. Sets up the LangChain MCP client and Bedrock model with authentication
405435
4. Creates a LangGraph agent with available tools
406436
5. Invokes the agent with the provided message
@@ -414,40 +444,75 @@ async def main():
414444
logger.info(f"Connecting to MCP server: {server_url}")
415445
logger.info(f"Using model: {args.model}")
416446
logger.info(f"Message: {args.message}")
417-
logger.info(f"Cognito User Pool ID: {redact_sensitive_value(args.user_pool_id)}")
418-
logger.info(f"Cognito Client ID: {redact_sensitive_value(args.client_id)}")
419-
logger.info(f"AWS Region: {args.region}")
420-
421-
# Generate Cognito M2M authentication token
422-
try:
423-
logger.info("Generating Cognito M2M authentication token...")
424-
token_data = generate_token(
425-
client_id=args.client_id,
426-
client_secret=args.client_secret,
427-
user_pool_id=args.user_pool_id,
428-
region=args.region,
429-
scopes=args.scopes
430-
)
431-
access_token = token_data.get('access_token')
432-
if not access_token:
433-
raise ValueError("No access token received from Cognito")
434-
logger.info("Successfully generated authentication token")
435-
except Exception as e:
436-
logger.error(f"Failed to generate authentication token: {e}")
437-
return
447+
logger.info(f"Authentication method: {'Session Cookie' if args.use_session_cookie else 'M2M Token'}")
448+
449+
# Initialize authentication variables
450+
access_token = None
451+
session_cookie = None
452+
auth_method = "session_cookie" if args.use_session_cookie else "m2m"
453+
454+
if args.use_session_cookie:
455+
# Load session cookie from file
456+
try:
457+
cookie_path = os.path.expanduser(args.session_cookie_file)
458+
with open(cookie_path, 'r') as f:
459+
session_cookie = f.read().strip()
460+
logger.info(f"Successfully loaded session cookie from {cookie_path}")
461+
except Exception as e:
462+
logger.error(f"Failed to load session cookie: {e}")
463+
return
464+
else:
465+
# Generate Cognito M2M authentication token
466+
logger.info(f"Cognito User Pool ID: {redact_sensitive_value(args.user_pool_id)}")
467+
logger.info(f"Cognito Client ID: {redact_sensitive_value(args.client_id)}")
468+
logger.info(f"AWS Region: {args.region}")
469+
470+
try:
471+
logger.info("Generating Cognito M2M authentication token...")
472+
token_data = generate_token(
473+
client_id=args.client_id,
474+
client_secret=args.client_secret,
475+
user_pool_id=args.user_pool_id,
476+
region=args.region,
477+
scopes=args.scopes,
478+
domain=args.domain
479+
)
480+
access_token = token_data.get('access_token')
481+
if not access_token:
482+
raise ValueError("No access token received from Cognito")
483+
logger.info("Successfully generated authentication token")
484+
except Exception as e:
485+
logger.error(f"Failed to generate authentication token: {e}")
486+
return
438487

439488
# Initialize the model
440489
model = ChatBedrockConverse(model_id=args.model, region_name='us-east-1')
441490

442491
try:
443-
# Prepare headers for MCP client authentication
444-
auth_headers = {
445-
'Authorization': f'Bearer {access_token}',
446-
'X-User-Pool-Id': args.user_pool_id,
447-
'X-Client-Id': args.client_id,
448-
'X-Region': args.region
449-
}
450-
logger.info(f"Using authentication headers: {auth_headers}")
492+
# Prepare headers for MCP client authentication based on method
493+
if args.use_session_cookie:
494+
auth_headers = {
495+
'Cookie': f'mcp_gateway_session={session_cookie}',
496+
'X-User-Pool-Id': args.user_pool_id or '',
497+
'X-Client-Id': args.client_id or '',
498+
'X-Region': args.region or 'us-east-1'
499+
}
500+
else:
501+
auth_headers = {
502+
'Authorization': f'Bearer {access_token}',
503+
'X-User-Pool-Id': args.user_pool_id,
504+
'X-Client-Id': args.client_id,
505+
'X-Region': args.region
506+
}
507+
508+
# Log redacted headers
509+
redacted_headers = {}
510+
for k, v in auth_headers.items():
511+
if k in ['Authorization', 'Cookie', 'X-User-Pool-Id', 'X-Client-Id']:
512+
redacted_headers[k] = redact_sensitive_value(v) if v else ''
513+
else:
514+
redacted_headers[k] = v
515+
logger.info(f"Using authentication headers: {redacted_headers}")
451516

452517
# Initialize MCP client with the server configuration and authentication headers
453518
client = MultiServerMCPClient(
@@ -478,14 +543,30 @@ async def main():
478543

479544
# Load and format the system prompt with the current time and MCP registry URL
480545
system_prompt_template = load_system_prompt()
481-
system_prompt = system_prompt_template.format(
482-
current_utc_time=current_utc_time,
483-
mcp_registry_url=args.mcp_registry_url,
484-
auth_token=access_token,
485-
user_pool_id=args.user_pool_id,
486-
client_id=args.client_id,
487-
region=args.region
488-
)
546+
547+
# Prepare authentication parameters for system prompt
548+
if args.use_session_cookie:
549+
system_prompt = system_prompt_template.format(
550+
current_utc_time=current_utc_time,
551+
mcp_registry_url=args.mcp_registry_url,
552+
auth_token='', # Not used for session cookie auth
553+
user_pool_id=args.user_pool_id or '',
554+
client_id=args.client_id or '',
555+
region=args.region or 'us-east-1',
556+
auth_method=auth_method,
557+
session_cookie=session_cookie
558+
)
559+
else:
560+
system_prompt = system_prompt_template.format(
561+
current_utc_time=current_utc_time,
562+
mcp_registry_url=args.mcp_registry_url,
563+
auth_token=access_token,
564+
user_pool_id=args.user_pool_id,
565+
client_id=args.client_id,
566+
region=args.region,
567+
auth_method=auth_method,
568+
session_cookie='' # Not used for M2M auth
569+
)
489570

490571
# Format the message with system message first
491572
formatted_messages = [

agents/system_prompt.txt

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ IMPORTANT: When using MCP registry tools that require authentication, use these
1313

1414
<credentials>
1515
Authentication credentials for invoke_mcp_tool (use these when calling invoke_mcp_tool):
16-
- auth_token: {auth_token}
16+
- auth_method: {auth_method}
17+
- auth_token: {auth_token} (for M2M authentication)
18+
- session_cookie: {session_cookie} (for session authentication)
1719
- user_pool_id: {user_pool_id}
1820
- client_id: {client_id}
1921
- region: {region}
@@ -29,7 +31,13 @@ You have access to both built-in tools and tools from MCP servers, including int
2931
<available_tools>
3032
You have direct access to these built-in tools:
3133
- calculator: For performing mathematical calculations and arithmetic operations
32-
- invoke_mcp_tool: For invoking tools on MCP servers using the MCP Registry URL, server name, tool name, and arguments
34+
- invoke_mcp_tool: For invoking tools on MCP servers. IMPORTANT: When calling invoke_mcp_tool, you MUST include ALL authentication parameters:
35+
* auth_token: {auth_token}
36+
* user_pool_id: {user_pool_id}
37+
* client_id: {client_id}
38+
* region: {region}
39+
* auth_method: {auth_method}
40+
* session_cookie: {session_cookie}
3341

3442
You also have access to tools loaded from the MCP server which may include:
3543
- intelligent_tool_finder: For discovering specialized tools when you need capabilities you don't have direct access to
@@ -55,7 +63,19 @@ Example workflow:
5563
1. Discover a tool: result = intelligent_tool_finder("current time timezone")
5664
2. The result provides details about a time tool on the "currenttime" MCP server.
5765
3. Always use the "service_path" path field for the server name while creating the arguments for the invoke_mcp_tool in the next step.
58-
4. Use invoke_mcp_tool to call it: invoke_mcp_tool("https://registry-url.com/mcpgw/sse", "/currenttime", "current_time_by_timezone", arguments with tz_name set to America/New_York)
66+
4. Use invoke_mcp_tool to call it with ALL required auth parameters:
67+
invoke_mcp_tool(
68+
mcp_registry_url="https://registry-url.com/mcpgw/sse",
69+
server_name="/currenttime",
70+
tool_name="current_time_by_timezone",
71+
arguments=dict(params=dict(tz_name="America/New_York")),
72+
auth_token=<use the auth_token from credentials section>,
73+
user_pool_id=<use the user_pool_id from credentials section>,
74+
client_id=<use the client_id from credentials section>,
75+
region=<use the region from credentials section>,
76+
auth_method=<use the auth_method from credentials section>,
77+
session_cookie=<use the session_cookie from credentials section>
78+
)
5979
</tool_discovery>
6080

6181
<workflow>

0 commit comments

Comments
 (0)