From 478d737f0a7ffc2f877d1a3195227aa146d83354 Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Tue, 20 Jan 2026 15:29:27 -0800 Subject: [PATCH 01/10] Add Obo Support --- python/openai/sample-agent/agent.py | 18 +++++++--- .../openai/sample-agent/host_agent_server.py | 35 +++++++++++-------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/python/openai/sample-agent/agent.py b/python/openai/sample-agent/agent.py index c510c3f6..4d8264e0 100644 --- a/python/openai/sample-agent/agent.py +++ b/python/openai/sample-agent/agent.py @@ -220,18 +220,22 @@ def _initialize_services(self): # return tool_service, auth_options async def setup_mcp_servers(self, auth: Authorization, auth_handler_name: str, context: TurnContext): - """Set up MCP server connections""" + """Set up MCP server connections based on authentication configuration""" try: - use_agentic_auth = os.getenv("USE_AGENTIC_AUTH", "false").lower() == "true" + + # Scenario 1: Agentic Authentication (Production) if use_agentic_auth: + logger.info("🔒 Using Agentic Authentication for MCP servers") self.agent = await self.tool_service.add_tool_servers_to_agent( agent=self.agent, auth=auth, auth_handler_name=auth_handler_name, context=context, ) - else: + # Scenario 2: OBO with Bearer Token (Development/Testing) + elif self.auth_options.bearer_token: + logger.info("🔑 Using OBO Bearer Token for MCP servers") self.agent = await self.tool_service.add_tool_servers_to_agent( agent=self.agent, auth=auth, @@ -239,9 +243,15 @@ async def setup_mcp_servers(self, auth: Authorization, auth_handler_name: str, c context=context, auth_token=self.auth_options.bearer_token, ) + # Scenario 3: No Authentication - Bare LLM only (no MCP servers) + else: + logger.warning("⚠️ No authentication available - running in bare LLM mode (no MCP servers)") + logger.info("💡 To enable MCP servers: Set USE_AGENTIC_AUTH=true OR provide BEARER_TOKEN") + # Agent already initialized without MCP tools - will use only base LLM capabilities except Exception as e: - logger.error(f"Error setting up MCP servers: {e}") + logger.error(f"❌ Error setting up MCP servers: {e}") + logger.warning("⚠️ Falling back to bare LLM mode without MCP servers") async def initialize(self): """Initialize the agent and MCP server connections""" diff --git a/python/openai/sample-agent/host_agent_server.py b/python/openai/sample-agent/host_agent_server.py index d124d980..1d2d830d 100644 --- a/python/openai/sample-agent/host_agent_server.py +++ b/python/openai/sample-agent/host_agent_server.py @@ -68,7 +68,9 @@ def __init__(self, agent_class: type[AgentInterface], *agent_args, **agent_kwarg if not check_agent_inheritance(agent_class): raise TypeError(f"Agent class {agent_class.__name__} must inherit from AgentInterface") - self.auth_handler_name = "AGENTIC" + # Only use auth handler when agentic auth is enabled + use_agentic_auth = os.getenv("USE_AGENTIC_AUTH", "false").lower() == "true" + self.auth_handler_name = "AGENTIC" if use_agentic_auth else None self.agent_class = agent_class self.agent_args = agent_args @@ -110,8 +112,9 @@ async def help_handler(context: TurnContext, _: TurnState): self.agent_app.conversation_update("membersAdded")(help_handler) self.agent_app.message("/help")(help_handler) - handler = [self.auth_handler_name] - @self.agent_app.activity("message", auth_handlers=handler) + # Only require auth handlers if agentic auth is enabled + handler_config = {"auth_handlers": [self.auth_handler_name]} if self.auth_handler_name else {} + @self.agent_app.activity("message", **handler_config) async def on_message(context: TurnContext, _: TurnState): """Handle all messages with the hosted agent""" try: @@ -125,18 +128,20 @@ async def on_message(context: TurnContext, _: TurnState): await context.send_activity(error_msg) return - exaau_token = await self.agent_app.auth.exchange_token( - context, - scopes=get_observability_authentication_scope(), - auth_handler_id=self.auth_handler_name, - ) - - # Cache the agentic token for Agent 365 Observability exporter use - cache_agentic_token( - tenant_id, - agent_id, - exaau_token.token, - ) + # Only exchange token if agentic auth is enabled + if self.auth_handler_name: + exaau_token = await self.agent_app.auth.exchange_token( + context, + scopes=get_observability_authentication_scope(), + auth_handler_id=self.auth_handler_name, + ) + + # Cache the agentic token for Agent 365 Observability exporter use + cache_agentic_token( + tenant_id, + agent_id, + exaau_token.token, + ) user_message = context.activity.text or "" logger.info(f"📨 Processing message: '{user_message}'") From 5e9ef062e1685e56b635ba118c325a3fa198a643 Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Tue, 20 Jan 2026 20:04:46 -0800 Subject: [PATCH 02/10] Add Obo Support --- python/openai/sample-agent/agent.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/python/openai/sample-agent/agent.py b/python/openai/sample-agent/agent.py index 4d8264e0..405e62ee 100644 --- a/python/openai/sample-agent/agent.py +++ b/python/openai/sample-agent/agent.py @@ -84,11 +84,15 @@ def __init__(self, openai_api_key: str | None = None): api_key=api_key, api_version="2025-01-01-preview", ) + # Use Azure deployment name for Azure OpenAI + model_name = os.getenv("AZURE_OPENAI_DEPLOYMENT", "gpt-4o-mini") else: self.openai_client = AsyncOpenAI(api_key=self.openai_api_key) + # Use model name for OpenAI + model_name = os.getenv("OPENAI_MODEL", "gpt-4o-mini") self.model = OpenAIChatCompletionsModel( - model=os.getenv("OPENAI_MODEL", "gpt-4o-mini"), openai_client=self.openai_client + model=model_name, openai_client=self.openai_client ) # Configure model settings (optional parameters) From 429ff626f7ce6986f5cb034d4cde0cb64c98ca01 Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Wed, 21 Jan 2026 10:14:42 -0800 Subject: [PATCH 03/10] add channels --- .../sample-agent/Agents/MyAgent.cs | 6 ++++++ .../sample-agent/ToolingManifest.json | 15 --------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/dotnet/semantic-kernel/sample-agent/Agents/MyAgent.cs b/dotnet/semantic-kernel/sample-agent/Agents/MyAgent.cs index ddfbe39c..c206d839 100644 --- a/dotnet/semantic-kernel/sample-agent/Agents/MyAgent.cs +++ b/dotnet/semantic-kernel/sample-agent/Agents/MyAgent.cs @@ -122,6 +122,12 @@ await A365OtelWrapper.InvokeObservedAgentOperation( { await TeamsMessageActivityAsync(agent365Agent, turnContext, turnState, cancellationToken); } + else if (turnContext.Activity.ChannelId.Channel == Channels.Emulator || + turnContext.Activity.ChannelId.Channel == Channels.Test) + { + var response = await agent365Agent.InvokeAgentAsync(turnContext.Activity.Text, new ChatHistory(), turnContext); + await OutputResponseAsync(turnContext, turnState, response, cancellationToken); + } else { await turnContext.SendActivityAsync(MessageFactory.Text($"Sorry, I do not know how to respond to messages from channel '{turnContext.Activity.ChannelId}'."), cancellationToken); diff --git a/dotnet/semantic-kernel/sample-agent/ToolingManifest.json b/dotnet/semantic-kernel/sample-agent/ToolingManifest.json index fb64e3fe..7c94ba62 100644 --- a/dotnet/semantic-kernel/sample-agent/ToolingManifest.json +++ b/dotnet/semantic-kernel/sample-agent/ToolingManifest.json @@ -2,21 +2,6 @@ "mcpServers": [ { "mcpServerName": "mcp_MailTools" - }, - { - "mcpServerName": "mcp_CalendarTools" - }, - { - "mcpServerName": "OneDriveMCPServer" - }, - { - "mcpServerName": "mcp_NLWeb" - }, - { - "mcpServerName": "mcp_KnowledgeTools" - }, - { - "mcpServerName": "mcp_MeServer" } ] } \ No newline at end of file From 934d2306fe1a999fdf7b884a0afd2b64f42f6f5a Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Wed, 21 Jan 2026 13:05:20 -0800 Subject: [PATCH 04/10] feat: Add graceful fallback when MCP tools fail to load When MCP tool loading fails (e.g., due to authentication issues in CI/CD), the agent now continues without tools rather than throwing an exception. This allows the agent to still respond to basic queries using just the LLM. --- .../sample-agent/Agents/Agent365Agent.cs | 77 +++++++++++-------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/dotnet/semantic-kernel/sample-agent/Agents/Agent365Agent.cs b/dotnet/semantic-kernel/sample-agent/Agents/Agent365Agent.cs index 3ca0569f..25336f10 100644 --- a/dotnet/semantic-kernel/sample-agent/Agents/Agent365Agent.cs +++ b/dotnet/semantic-kernel/sample-agent/Agents/Agent365Agent.cs @@ -1,21 +1,21 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Agent365SemanticKernelSampleAgent.Plugins; -using Microsoft.Agents.A365.Tooling.Extensions.SemanticKernel.Services; -using Microsoft.Agents.Builder; -using Microsoft.Agents.Builder.App.UserAuth; -using Microsoft.Extensions.Configuration; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; -using Microsoft.SemanticKernel.ChatCompletion; -using Microsoft.SemanticKernel.Connectors.OpenAI; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Text.Json; -using System.Text.Json.Nodes; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Agent365SemanticKernelSampleAgent.Plugins; +using Microsoft.Agents.A365.Tooling.Extensions.SemanticKernel.Services; +using Microsoft.Agents.Builder; +using Microsoft.Agents.Builder.App.UserAuth; +using Microsoft.Extensions.Configuration; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.ChatCompletion; +using Microsoft.SemanticKernel.Connectors.OpenAI; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; using System.Threading.Tasks; namespace Agent365SemanticKernelSampleAgent.Agents; @@ -54,10 +54,10 @@ public static async Task CreateA365AgentWrapper(Kernel kernel, IS return _agent; } - public static bool TryGetBearerTokenForDevelopment(out string? bearerToken) - { - bearerToken = Environment.GetEnvironmentVariable("BEARER_TOKEN"); - return !string.IsNullOrEmpty(bearerToken); + public static bool TryGetBearerTokenForDevelopment(out string? bearerToken) + { + bearerToken = Environment.GetEnvironmentVariable("BEARER_TOKEN"); + return !string.IsNullOrEmpty(bearerToken); } /// @@ -79,17 +79,28 @@ public async Task InitializeAgent365Agent(Kernel kernel, IServiceProvider servic // Provide the tool service with necessary parameters to connect to A365 this._kernel.ImportPluginFromType(); - await turnContext.StreamingResponse.QueueInformativeUpdateAsync("Loading tools..."); - - if (TryGetBearerTokenForDevelopment(out var bearerToken)) - { - // Development mode: Use bearer token from environment variable for simplified local testing - await toolService.AddToolServersToAgentAsync(kernel, userAuthorization, authHandlerName, turnContext, bearerToken); - } - else - { - // Production mode: Use standard authentication flow (Client Credentials, Managed Identity, or Federated Credentials) - await toolService.AddToolServersToAgentAsync(kernel, userAuthorization, authHandlerName, turnContext); + await turnContext.StreamingResponse.QueueInformativeUpdateAsync("Loading tools..."); + + try + { + if (TryGetBearerTokenForDevelopment(out var bearerToken)) + { + // Development mode: Use bearer token from environment variable for simplified local testing + await toolService.AddToolServersToAgentAsync(kernel, userAuthorization, authHandlerName, turnContext, bearerToken); + } + else + { + // Production mode: Use standard authentication flow (Client Credentials, Managed Identity, or Federated Credentials) + await toolService.AddToolServersToAgentAsync(kernel, userAuthorization, authHandlerName, turnContext); + } + } + catch (Exception ex) + { + // Graceful fallback: Log the error but continue without MCP tools + // This allows the agent to still respond to basic queries using only the LLM + System.Diagnostics.Debug.WriteLine($"Warning: Failed to load MCP tools: {ex.Message}"); + Console.WriteLine($"Warning: MCP tools unavailable - running in bare LLM mode. Error: {ex.Message}"); + await turnContext.StreamingResponse.QueueInformativeUpdateAsync("Note: Some tools are not available. Running in basic mode."); } } else From 60322dfe94d419aa255e50644e422b4730b8bec6 Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Wed, 21 Jan 2026 13:26:50 -0800 Subject: [PATCH 05/10] refactor: Address PR review feedback from Pontemonti Changes: - Remove USE_AGENTIC_AUTH flag, use AUTH_HANDLER_NAME instead - Fix terminology: 'bearer token from config' instead of 'OBO' - Add explicit SKIP_MCP_SERVERS flag for running without tools - Implement proper priority: bearer_token > auth_handler > skip > localhost - Fix comments about auth handler requirements - Always configure auth handlers when AUTH_HANDLER_NAME is set --- python/openai/sample-agent/agent.py | 57 ++++++++++++++----- .../openai/sample-agent/host_agent_server.py | 18 ++++-- 2 files changed, 54 insertions(+), 21 deletions(-) diff --git a/python/openai/sample-agent/agent.py b/python/openai/sample-agent/agent.py index 405e62ee..0cfdf493 100644 --- a/python/openai/sample-agent/agent.py +++ b/python/openai/sample-agent/agent.py @@ -224,38 +224,65 @@ def _initialize_services(self): # return tool_service, auth_options async def setup_mcp_servers(self, auth: Authorization, auth_handler_name: str, context: TurnContext): - """Set up MCP server connections based on authentication configuration""" + """Set up MCP server connections based on authentication configuration. + + Authentication priority: + 1. Bearer token from config (BEARER_TOKEN) - for local development/testing + 2. Auth handler (auth_handler_name) - for production agentic auth + 3. Skip MCP servers (SKIP_MCP_SERVERS=true) - run without tooling + 4. No auth (localhost only) - connect to mock/local MCP servers without auth + """ + skip_mcp = os.getenv("SKIP_MCP_SERVERS", "false").lower() == "true" + is_localhost = os.getenv("MCP_PLATFORM_ENDPOINT", "").startswith("http://localhost") or \ + os.getenv("ASPNETCORE_ENVIRONMENT", "").lower() == "development" + try: - use_agentic_auth = os.getenv("USE_AGENTIC_AUTH", "false").lower() == "true" - - # Scenario 1: Agentic Authentication (Production) - if use_agentic_auth: - logger.info("🔒 Using Agentic Authentication for MCP servers") + # Priority 1: Bearer token provided in config (for local dev/testing) + if self.auth_options.bearer_token: + logger.info("🔑 Using bearer token from config for MCP servers") self.agent = await self.tool_service.add_tool_servers_to_agent( agent=self.agent, auth=auth, auth_handler_name=auth_handler_name, context=context, + auth_token=self.auth_options.bearer_token, ) - # Scenario 2: OBO with Bearer Token (Development/Testing) - elif self.auth_options.bearer_token: - logger.info("🔑 Using OBO Bearer Token for MCP servers") + # Priority 2: Auth handler configured (production agentic auth) + elif auth_handler_name: + logger.info(f"🔒 Using auth handler '{auth_handler_name}' for MCP servers") + self.agent = await self.tool_service.add_tool_servers_to_agent( + agent=self.agent, + auth=auth, + auth_handler_name=auth_handler_name, + context=context, + ) + # Priority 3: Explicitly skip MCP servers + elif skip_mcp: + logger.info("⏭️ SKIP_MCP_SERVERS=true - running without MCP tools") + # Agent already initialized without MCP tools + # Priority 4: Localhost without auth (for mock MCP servers) + elif is_localhost: + logger.warning("⚠️ Running on localhost without auth - attempting to connect to MCP servers without authentication") + logger.info("💡 For authenticated access: provide BEARER_TOKEN or configure auth handler") + # Attempt to connect without auth (works with mock servers) self.agent = await self.tool_service.add_tool_servers_to_agent( agent=self.agent, auth=auth, auth_handler_name=auth_handler_name, context=context, - auth_token=self.auth_options.bearer_token, ) - # Scenario 3: No Authentication - Bare LLM only (no MCP servers) + # No valid configuration else: - logger.warning("⚠️ No authentication available - running in bare LLM mode (no MCP servers)") - logger.info("💡 To enable MCP servers: Set USE_AGENTIC_AUTH=true OR provide BEARER_TOKEN") - # Agent already initialized without MCP tools - will use only base LLM capabilities + logger.error("❌ No authentication configured and not running on localhost") + logger.info("💡 Options: 1) Provide BEARER_TOKEN, 2) Configure auth handler, 3) Set SKIP_MCP_SERVERS=true") + raise ValueError("MCP authentication required in production. Set SKIP_MCP_SERVERS=true to run without tools.") except Exception as e: logger.error(f"❌ Error setting up MCP servers: {e}") - logger.warning("⚠️ Falling back to bare LLM mode without MCP servers") + if skip_mcp or is_localhost: + logger.warning("⚠️ Falling back to bare LLM mode without MCP servers") + else: + raise async def initialize(self): """Initialize the agent and MCP server connections""" diff --git a/python/openai/sample-agent/host_agent_server.py b/python/openai/sample-agent/host_agent_server.py index 1d2d830d..3fdde204 100644 --- a/python/openai/sample-agent/host_agent_server.py +++ b/python/openai/sample-agent/host_agent_server.py @@ -68,9 +68,15 @@ def __init__(self, agent_class: type[AgentInterface], *agent_args, **agent_kwarg if not check_agent_inheritance(agent_class): raise TypeError(f"Agent class {agent_class.__name__} must inherit from AgentInterface") - # Only use auth handler when agentic auth is enabled - use_agentic_auth = os.getenv("USE_AGENTIC_AUTH", "false").lower() == "true" - self.auth_handler_name = "AGENTIC" if use_agentic_auth else None + # Auth handler name can be configured via environment or defaults to AGENTIC + # Set AUTH_HANDLER_NAME to empty string to disable auth handlers + default_auth_handler = "AGENTIC" + self.auth_handler_name = os.getenv("AUTH_HANDLER_NAME", default_auth_handler) + if self.auth_handler_name == "": + self.auth_handler_name = None + logger.info("🔓 Auth handler disabled (AUTH_HANDLER_NAME is empty)") + else: + logger.info(f"🔐 Using auth handler: {self.auth_handler_name}") self.agent_class = agent_class self.agent_args = agent_args @@ -112,7 +118,7 @@ async def help_handler(context: TurnContext, _: TurnState): self.agent_app.conversation_update("membersAdded")(help_handler) self.agent_app.message("/help")(help_handler) - # Only require auth handlers if agentic auth is enabled + # Configure auth handlers - required for token exchange when auth_handler_name is set handler_config = {"auth_handlers": [self.auth_handler_name]} if self.auth_handler_name else {} @self.agent_app.activity("message", **handler_config) async def on_message(context: TurnContext, _: TurnState): @@ -128,7 +134,7 @@ async def on_message(context: TurnContext, _: TurnState): await context.send_activity(error_msg) return - # Only exchange token if agentic auth is enabled + # Exchange token for observability if auth handler is configured if self.auth_handler_name: exaau_token = await self.agent_app.auth.exchange_token( context, @@ -213,7 +219,7 @@ def create_auth_configuration(self) -> AgentAuthConfiguration | None: if environ.get("BEARER_TOKEN"): logger.info( - "🔑 BEARER_TOKEN present but incomplete app registration; continuing in anonymous dev mode" + "🔑 BEARER_TOKEN present - will use for MCP server authentication" ) else: logger.warning("⚠️ No authentication env vars found; running anonymous") From b2a29bb0e9f239a6a705b406fc1fcc34615b6759 Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Wed, 21 Jan 2026 13:40:40 -0800 Subject: [PATCH 06/10] fix: Default AUTH_HANDLER_NAME to empty instead of AGENTIC The previous change broke E2E tests because AGENTIC auth handler was being used by default but not configured in the test environment. Now defaults to no auth handler - users must explicitly set AUTH_HANDLER_NAME=AGENTIC for production agentic authentication. --- python/openai/sample-agent/host_agent_server.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/python/openai/sample-agent/host_agent_server.py b/python/openai/sample-agent/host_agent_server.py index 3fdde204..4df50db0 100644 --- a/python/openai/sample-agent/host_agent_server.py +++ b/python/openai/sample-agent/host_agent_server.py @@ -68,15 +68,13 @@ def __init__(self, agent_class: type[AgentInterface], *agent_args, **agent_kwarg if not check_agent_inheritance(agent_class): raise TypeError(f"Agent class {agent_class.__name__} must inherit from AgentInterface") - # Auth handler name can be configured via environment or defaults to AGENTIC - # Set AUTH_HANDLER_NAME to empty string to disable auth handlers - default_auth_handler = "AGENTIC" - self.auth_handler_name = os.getenv("AUTH_HANDLER_NAME", default_auth_handler) - if self.auth_handler_name == "": - self.auth_handler_name = None - logger.info("🔓 Auth handler disabled (AUTH_HANDLER_NAME is empty)") - else: + # Auth handler name can be configured via environment + # Defaults to empty (no auth handler) - set AUTH_HANDLER_NAME=AGENTIC for production agentic auth + self.auth_handler_name = os.getenv("AUTH_HANDLER_NAME", "") or None + if self.auth_handler_name: logger.info(f"🔐 Using auth handler: {self.auth_handler_name}") + else: + logger.info("🔓 No auth handler configured (AUTH_HANDLER_NAME not set)") self.agent_class = agent_class self.agent_args = agent_args From 3345a329bc8089b3558e1d580270e38059ffb348 Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Wed, 21 Jan 2026 13:50:24 -0800 Subject: [PATCH 07/10] fix: Always gracefully fallback to bare LLM mode when MCP fails Instead of throwing errors to the user when MCP servers fail to connect, the agent now gracefully falls back to bare LLM mode. This allows the agent to still respond to basic queries even without MCP tools. Simplified priority logic: 1. SKIP_MCP_SERVERS=true - explicitly skip MCP 2. BEARER_TOKEN - use token from config 3. AUTH_HANDLER_NAME - use agentic auth 4. No auth - skip MCP gracefully (log warning) Any MCP connection errors are caught and logged, allowing the agent to continue operating with just the base LLM capabilities. --- python/openai/sample-agent/agent.py | 46 +++++++++++------------------ 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/python/openai/sample-agent/agent.py b/python/openai/sample-agent/agent.py index 0cfdf493..098c970b 100644 --- a/python/openai/sample-agent/agent.py +++ b/python/openai/sample-agent/agent.py @@ -230,14 +230,20 @@ async def setup_mcp_servers(self, auth: Authorization, auth_handler_name: str, c 1. Bearer token from config (BEARER_TOKEN) - for local development/testing 2. Auth handler (auth_handler_name) - for production agentic auth 3. Skip MCP servers (SKIP_MCP_SERVERS=true) - run without tooling - 4. No auth (localhost only) - connect to mock/local MCP servers without auth + 4. No auth - gracefully skip MCP and run in bare LLM mode + + If MCP connection fails for any reason, the agent will gracefully fall back + to bare LLM mode without MCP tools. """ skip_mcp = os.getenv("SKIP_MCP_SERVERS", "false").lower() == "true" - is_localhost = os.getenv("MCP_PLATFORM_ENDPOINT", "").startswith("http://localhost") or \ - os.getenv("ASPNETCORE_ENVIRONMENT", "").lower() == "development" + + # Priority 1: Explicitly skip MCP servers + if skip_mcp: + logger.info("⏭️ SKIP_MCP_SERVERS=true - running without MCP tools") + return try: - # Priority 1: Bearer token provided in config (for local dev/testing) + # Priority 2: Bearer token provided in config (for local dev/testing) if self.auth_options.bearer_token: logger.info("🔑 Using bearer token from config for MCP servers") self.agent = await self.tool_service.add_tool_servers_to_agent( @@ -247,7 +253,7 @@ async def setup_mcp_servers(self, auth: Authorization, auth_handler_name: str, c context=context, auth_token=self.auth_options.bearer_token, ) - # Priority 2: Auth handler configured (production agentic auth) + # Priority 3: Auth handler configured (production agentic auth) elif auth_handler_name: logger.info(f"🔒 Using auth handler '{auth_handler_name}' for MCP servers") self.agent = await self.tool_service.add_tool_servers_to_agent( @@ -256,33 +262,17 @@ async def setup_mcp_servers(self, auth: Authorization, auth_handler_name: str, c auth_handler_name=auth_handler_name, context=context, ) - # Priority 3: Explicitly skip MCP servers - elif skip_mcp: - logger.info("⏭️ SKIP_MCP_SERVERS=true - running without MCP tools") - # Agent already initialized without MCP tools - # Priority 4: Localhost without auth (for mock MCP servers) - elif is_localhost: - logger.warning("⚠️ Running on localhost without auth - attempting to connect to MCP servers without authentication") - logger.info("💡 For authenticated access: provide BEARER_TOKEN or configure auth handler") - # Attempt to connect without auth (works with mock servers) - self.agent = await self.tool_service.add_tool_servers_to_agent( - agent=self.agent, - auth=auth, - auth_handler_name=auth_handler_name, - context=context, - ) - # No valid configuration + # Priority 4: No auth configured - skip MCP and run bare LLM else: - logger.error("❌ No authentication configured and not running on localhost") - logger.info("💡 Options: 1) Provide BEARER_TOKEN, 2) Configure auth handler, 3) Set SKIP_MCP_SERVERS=true") - raise ValueError("MCP authentication required in production. Set SKIP_MCP_SERVERS=true to run without tools.") + logger.warning("⚠️ No authentication configured - running in bare LLM mode without MCP tools") + logger.info("💡 To enable MCP: provide BEARER_TOKEN or configure AUTH_HANDLER_NAME") + # Agent already initialized without MCP tools except Exception as e: + # Always gracefully fall back to bare LLM mode on any MCP error logger.error(f"❌ Error setting up MCP servers: {e}") - if skip_mcp or is_localhost: - logger.warning("⚠️ Falling back to bare LLM mode without MCP servers") - else: - raise + logger.warning("⚠️ Falling back to bare LLM mode without MCP servers") + # Agent continues with base LLM capabilities only async def initialize(self): """Initialize the agent and MCP server connections""" From e79e93588019b3c457c6965306b62ab2308588ed Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Wed, 21 Jan 2026 13:57:41 -0800 Subject: [PATCH 08/10] remove SKIP_MCP_SERVERS --- python/openai/sample-agent/agent.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/python/openai/sample-agent/agent.py b/python/openai/sample-agent/agent.py index 098c970b..0e5f774d 100644 --- a/python/openai/sample-agent/agent.py +++ b/python/openai/sample-agent/agent.py @@ -229,21 +229,13 @@ async def setup_mcp_servers(self, auth: Authorization, auth_handler_name: str, c Authentication priority: 1. Bearer token from config (BEARER_TOKEN) - for local development/testing 2. Auth handler (auth_handler_name) - for production agentic auth - 3. Skip MCP servers (SKIP_MCP_SERVERS=true) - run without tooling - 4. No auth - gracefully skip MCP and run in bare LLM mode + 3. No auth - gracefully skip MCP and run in bare LLM mode If MCP connection fails for any reason, the agent will gracefully fall back to bare LLM mode without MCP tools. """ - skip_mcp = os.getenv("SKIP_MCP_SERVERS", "false").lower() == "true" - - # Priority 1: Explicitly skip MCP servers - if skip_mcp: - logger.info("⏭️ SKIP_MCP_SERVERS=true - running without MCP tools") - return - try: - # Priority 2: Bearer token provided in config (for local dev/testing) + # Priority 1: Bearer token provided in config (for local dev/testing) if self.auth_options.bearer_token: logger.info("🔑 Using bearer token from config for MCP servers") self.agent = await self.tool_service.add_tool_servers_to_agent( @@ -253,7 +245,7 @@ async def setup_mcp_servers(self, auth: Authorization, auth_handler_name: str, c context=context, auth_token=self.auth_options.bearer_token, ) - # Priority 3: Auth handler configured (production agentic auth) + # Priority 2: Auth handler configured (production agentic auth) elif auth_handler_name: logger.info(f"🔒 Using auth handler '{auth_handler_name}' for MCP servers") self.agent = await self.tool_service.add_tool_servers_to_agent( @@ -262,7 +254,7 @@ async def setup_mcp_servers(self, auth: Authorization, auth_handler_name: str, c auth_handler_name=auth_handler_name, context=context, ) - # Priority 4: No auth configured - skip MCP and run bare LLM + # Priority 3: No auth configured - skip MCP and run bare LLM else: logger.warning("⚠️ No authentication configured - running in bare LLM mode without MCP tools") logger.info("💡 To enable MCP: provide BEARER_TOKEN or configure AUTH_HANDLER_NAME") From ddb19c32cd31d1cd87145a324beb335517b81e5e Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Wed, 21 Jan 2026 16:50:40 -0800 Subject: [PATCH 09/10] adding skip on errors flag --- .../sample-agent/Agents/Agent365Agent.cs | 37 ++++++++++++++++--- python/openai/sample-agent/agent.py | 25 +++++++++++-- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/dotnet/semantic-kernel/sample-agent/Agents/Agent365Agent.cs b/dotnet/semantic-kernel/sample-agent/Agents/Agent365Agent.cs index 25336f10..582cb73f 100644 --- a/dotnet/semantic-kernel/sample-agent/Agents/Agent365Agent.cs +++ b/dotnet/semantic-kernel/sample-agent/Agents/Agent365Agent.cs @@ -60,6 +60,24 @@ public static bool TryGetBearerTokenForDevelopment(out string? bearerToken) return !string.IsNullOrEmpty(bearerToken); } + /// + /// Checks if graceful fallback to bare LLM mode is enabled when MCP tools fail to load. + /// This is only allowed in Development environment AND when SKIP_TOOLING_ON_ERRORS is explicitly set to "true". + /// + private static bool ShouldSkipToolingOnErrors() + { + var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? + Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? + "Production"; + + var skipToolingOnErrors = Environment.GetEnvironmentVariable("SKIP_TOOLING_ON_ERRORS"); + + // Only allow skipping tooling errors in Development mode AND when explicitly enabled + return environment.Equals("Development", StringComparison.OrdinalIgnoreCase) && + !string.IsNullOrEmpty(skipToolingOnErrors) && + skipToolingOnErrors.Equals("true", StringComparison.OrdinalIgnoreCase); + } + /// /// /// @@ -96,11 +114,20 @@ public async Task InitializeAgent365Agent(Kernel kernel, IServiceProvider servic } catch (Exception ex) { - // Graceful fallback: Log the error but continue without MCP tools - // This allows the agent to still respond to basic queries using only the LLM - System.Diagnostics.Debug.WriteLine($"Warning: Failed to load MCP tools: {ex.Message}"); - Console.WriteLine($"Warning: MCP tools unavailable - running in bare LLM mode. Error: {ex.Message}"); - await turnContext.StreamingResponse.QueueInformativeUpdateAsync("Note: Some tools are not available. Running in basic mode."); + // Only allow graceful fallback in Development mode when SKIP_TOOLING_ON_ERRORS is explicitly enabled + if (ShouldSkipToolingOnErrors()) + { + // Graceful fallback: Log the error but continue without MCP tools + // This allows the agent to still respond to basic queries using only the LLM + System.Diagnostics.Debug.WriteLine($"Warning: Failed to load MCP tools: {ex.Message}"); + Console.WriteLine($"Warning: MCP tools unavailable - running in bare LLM mode. Error: {ex.Message}"); + await turnContext.StreamingResponse.QueueInformativeUpdateAsync("Note: Some tools are not available. Running in basic mode."); + } + else + { + // In production or when SKIP_TOOLING_ON_ERRORS is not enabled, fail fast + throw; + } } } else diff --git a/python/openai/sample-agent/agent.py b/python/openai/sample-agent/agent.py index 0e5f774d..9cf2ec5e 100644 --- a/python/openai/sample-agent/agent.py +++ b/python/openai/sample-agent/agent.py @@ -65,6 +65,18 @@ class OpenAIAgentWithMCP(AgentInterface): # ========================================================================= # + @staticmethod + def should_skip_tooling_on_errors() -> bool: + """ + Checks if graceful fallback to bare LLM mode is enabled when MCP tools fail to load. + This is only allowed in Development environment AND when SKIP_TOOLING_ON_ERRORS is explicitly set to "true". + """ + environment = os.getenv("ENVIRONMENT", os.getenv("ASPNETCORE_ENVIRONMENT", "Production")) + skip_tooling_on_errors = os.getenv("SKIP_TOOLING_ON_ERRORS", "").lower() + + # Only allow skipping tooling errors in Development mode AND when explicitly enabled + return environment.lower() == "development" and skip_tooling_on_errors == "true" + def __init__(self, openai_api_key: str | None = None): self.openai_api_key = openai_api_key or os.getenv("OPENAI_API_KEY") if not self.openai_api_key and ( @@ -261,10 +273,15 @@ async def setup_mcp_servers(self, auth: Authorization, auth_handler_name: str, c # Agent already initialized without MCP tools except Exception as e: - # Always gracefully fall back to bare LLM mode on any MCP error - logger.error(f"❌ Error setting up MCP servers: {e}") - logger.warning("⚠️ Falling back to bare LLM mode without MCP servers") - # Agent continues with base LLM capabilities only + # Only allow graceful fallback in Development mode when SKIP_TOOLING_ON_ERRORS is explicitly enabled + if self.should_skip_tooling_on_errors(): + logger.error(f"❌ Error setting up MCP servers: {e}") + logger.warning("⚠️ Falling back to bare LLM mode without MCP servers (SKIP_TOOLING_ON_ERRORS=true)") + # Agent continues with base LLM capabilities only + else: + # In production or when SKIP_TOOLING_ON_ERRORS is not enabled, fail fast + logger.error(f"❌ Error setting up MCP servers: {e}") + raise async def initialize(self): """Initialize the agent and MCP server connections""" From eaa7fadd5feb7547e594875389e18cffdd6336f7 Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Thu, 22 Jan 2026 13:29:44 -0800 Subject: [PATCH 10/10] use override --- .../openai/sample-agent/local_authentication_options.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/python/openai/sample-agent/local_authentication_options.py b/python/openai/sample-agent/local_authentication_options.py index a732a7d6..111753e4 100644 --- a/python/openai/sample-agent/local_authentication_options.py +++ b/python/openai/sample-agent/local_authentication_options.py @@ -58,11 +58,18 @@ def from_environment( LocalAuthenticationOptions instance with values from environment. """ # Load .env file (automatically searches current and parent directories) - load_dotenv() + load_dotenv(override=True) # Force reload to pick up changes bearer_token = os.getenv(token_var, "") print(f"🔧 Bearer Token: {'***' if bearer_token else 'NOT SET'}") + + # DEBUG: Print token details + if bearer_token: + print(f"🔍 DEBUG: Token loaded from env, length: {len(bearer_token)}") + print(f"🔍 DEBUG: Token first 50 chars: {bearer_token[:50]}...") + else: + print(f"⚠️ DEBUG: No BEARER_TOKEN found in environment!") return cls(bearer_token=bearer_token)