Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6b18253
Add Azure OpenAI Service support with configuration and provider inte…
pablotoledo May 12, 2025
61b5ce7
Refactor code structure for improved readability and maintainability
pablotoledo May 12, 2025
3ac57a1
Implement code changes to enhance functionality and improve performance
pablotoledo May 12, 2025
aef77cd
Add unit tests for AzureOpenAIAugmentedLLM functionality
pablotoledo May 12, 2025
e375698
Refactor AzureOpenAIAugmentedLLM to use AzureOpenAI client and enhanc…
pablotoledo May 12, 2025
8be1100
Implement code changes to enhance functionality and improve performance
pablotoledo May 12, 2025
77ab540
Refactor AzureOpenAIAugmentedLLM initialization to improve resource n…
pablotoledo May 12, 2025
e9fae1d
Add AzureOpenAIAugmentedLLM provider and update related tests for int…
pablotoledo May 12, 2025
00cce18
Update Azure OpenAI configuration and restore pyproject.toml to main …
pablotoledo May 13, 2025
42a391b
Enhance Azure OpenAI integration by adding support for DefaultAzureCr…
pablotoledo May 13, 2025
a67c472
Enhance AzureOpenAIAugmentedLLM to utilize DefaultAzureCredential for…
pablotoledo May 13, 2025
5869e4a
Add support for Azure DefaultAzureCredential in check_api_keys; enhan…
pablotoledo May 13, 2025
d276fe1
pre-commit ruff linting check
pablotoledo May 13, 2025
a839ee4
Merge branch 'main' into pr/pablotoledo/160
evalstate May 13, 2025
0c74dab
add show assistant message
evalstate May 13, 2025
8f9343c
Fix docstring in _extract_resource_name function for clarity and corr…
pablotoledo May 13, 2025
c3581b8
Merge branch 'feat/azureai-integration' of https://github.com/pabloto…
pablotoledo May 13, 2025
fb08c3a
Enhance Azure OpenAI integration: update configuration instructions, …
pablotoledo May 13, 2025
0be91fb
pre-commit run over changed files in this PR
pablotoledo May 13, 2025
9c3d4ee
Merge branch 'main' into pr/pablotoledo/160
evalstate May 13, 2025
288ece8
fix zero-arg tools for azure
evalstate May 13, 2025
0c76ff7
Merge remote-tracking branch 'origin/main' into pr/pablotoledo/160
evalstate May 13, 2025
171412a
Refactor AzureOpenAIAugmentedLLM: streamline authentication handling …
pablotoledo May 13, 2025
cd4e21a
Fix comment language in test_openai_client_with_default_azure_credential
pablotoledo May 14, 2025
4e95246
Merge branch 'evalstate:main' into feat/azureai-integration
pablotoledo May 14, 2025
68d5fab
Merge branch 'feat/azureai-integration' of https://github.com/pabloto…
evalstate May 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions examples/azure-openai/fastagent.config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# NOTE: Since version X.X, the Azure OpenAI integration in FastAgent reuses the OpenAI logic.
# Authentication (API Key or DefaultAzureCredential) and connectivity are managed automatically.
# You only need to configure the 'azure' section as indicated below; the system will select the appropriate method.
#
# Example configuration for Azure OpenAI in fast-agent
#
# There are three supported authentication/configuration modes for Azure OpenAI:
#
# 1. Using 'resource_name' and 'api_key' (recommended for most users)
# 2. Using 'base_url' and 'api_key' (for custom endpoints or sovereign clouds)
# 3. Using 'base_url' and DefaultAzureCredential (for managed identity, Azure CLI, etc.)
#
# Use ONLY one of the parameters: 'resource_name' or 'base_url'.
# - If you define 'base_url', it will be used directly as the endpoint and 'resource_name' will be ignored.
# - If you define 'resource_name' (and not 'base_url'), the endpoint will be constructed automatically.
# - If both are missing, the configuration is invalid.
#
# Do not include both at the same time.

# --- OPTION 1: Using resource_name and api_key (recommended for standard Azure) ---
default_model: "azure.my-deployment"

azure:
api_key: "YOUR_AZURE_OPENAI_API_KEY"
resource_name: "your-resource-name"
azure_deployment: "my-deployment"
api_version: "2023-05-15"
# Do not include base_url if you use resource_name

# --- OPTION 2: Using base_url and api_key (for custom endpoints or sovereign clouds) ---
# default_model: "azure.my-deployment"
#
# azure:
# api_key: "YOUR_AZURE_OPENAI_API_KEY"
# base_url: "https://your-resource-name.openai.azure.com/"
# azure_deployment: "my-deployment"
# api_version: "2023-05-15"
# # Do not include resource_name if you use base_url

# --- OPTION 3: Using base_url and DefaultAzureCredential (for managed identity, Azure CLI, etc.) ---
# Requires the 'azure-identity' package to be installed.
# No api_key or resource_name should be present in this mode.
# base_url is required and must be the full endpoint URL.
#
# default_model: "azure.my-deployment"
#
# azure:
# use_default_azure_credential: true
# base_url: "https://your-resource-name.openai.azure.com/"
# azure_deployment: "my-deployment"
# api_version: "2023-05-15"
# # Do not include api_key or resource_name in this mode

# You can add other providers or settings as needed.
15 changes: 12 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,24 @@ dependencies = [
"typer>=0.15.1",
"anthropic>=0.49.0",
"openai>=1.63.2",
"azure-identity>=1.14.0",
"prompt-toolkit>=3.0.50",
"aiohttp>=3.11.13",
"a2a-types>=0.1.0",
"opentelemetry-instrumentation-openai>=0.39.3",
"opentelemetry-instrumentation-anthropic>=0.39.3",
"opentelemetry-instrumentation-openai>=0.39.3; python_version >= '3.10' and python_version < '4.0'",
"opentelemetry-instrumentation-anthropic>=0.39.3; python_version >= '3.10' and python_version < '4.0'",
"tensorzero>=2025.4.7",
"opentelemetry-instrumentation-mcp>=0.40.3",
"opentelemetry-instrumentation-mcp>=0.40.3; python_version >= '3.10' and python_version < '4.0'",
]

[project.optional-dependencies]
openai = [
"openai>=1.58.1",
]
# For Azure OpenAI with DefaultAzureCredential support, install with: pip install fast-agent-mcp[azure]
azure = [
"azure-identity>=1.14.0"
]
dev = [
"anthropic>=0.42.0",
"pre-commit>=4.0.1",
Expand All @@ -57,6 +62,10 @@ build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/mcp_agent"]

[[tool.poetry.packages]]
include = "mcp_agent"
from = "src"

[tool.hatch.build]
include = [
"src/mcp_agent/**/*.py",
Expand Down
89 changes: 61 additions & 28 deletions src/mcp_agent/cli/commands/check_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,42 +92,73 @@ def get_secrets_summary(secrets_path: Optional[Path]) -> dict:
return result


def check_api_keys(secrets_summary: dict) -> dict:
"""Check if API keys are configured in secrets file or environment."""
def check_api_keys(secrets_summary: dict, config_summary: dict) -> dict:
"""Check if API keys are configured in secrets file or environment, including Azure DefaultAzureCredential.
Now also checks Azure config in main config file for retrocompatibility.
"""
import os

# Initialize results dict using Provider enum values
results = {
provider.value: {"env": None, "config": None}
provider.value: {"env": "", "config": ""}
for provider in Provider
if provider != Provider.FAST_AGENT
} # Include GENERIC but exclude FAST_AGENT
}

# Get secrets if available
secrets = secrets_summary.get("secrets", {})
secrets_status = secrets_summary.get("status", "not_found")
# Get config if available
config = config_summary if config_summary.get("status") == "parsed" else {}
config_azure = {}
if config and "azure" in config.get("config", {}):
config_azure = config["config"]["azure"]

# Check both environment variables and config file for each provider
for provider_value in results:
# Check environment variables using ProviderKeyManager
env_key_name = ProviderKeyManager.get_env_key_name(provider_value)
env_key_value = os.environ.get(env_key_name)
if env_key_value:
# Store the last 5 characters if key is long enough
if len(env_key_value) > 5:
results[provider_value]["env"] = f"...{env_key_value[-5:]}"
else:
results[provider_value]["env"] = "...***"

# Check secrets file if it was parsed successfully
if secrets_status == "parsed":
config_key = ProviderKeyManager.get_config_file_key(provider_value, secrets)
if config_key and config_key != API_KEY_HINT_TEXT:
# Store the last 5 characters if key is long enough
if len(config_key) > 5:
results[provider_value]["config"] = f"...{config_key[-5:]}"
# Special handling for Azure: support api_key and DefaultAzureCredential
if provider_value == "azure":
# Prefer secrets if present, else fallback to config
azure_cfg = {}
if secrets_status == "parsed" and "azure" in secrets:
azure_cfg = secrets.get("azure", {})
elif config_azure:
azure_cfg = config_azure

use_default_cred = azure_cfg.get("use_default_azure_credential", False)
base_url = azure_cfg.get("base_url")
api_key = azure_cfg.get("api_key")
# DefaultAzureCredential mode
if use_default_cred and base_url:
results[provider_value]["config"] = "DefaultAzureCredential"
# API key mode (retrocompatible)
if api_key and api_key != API_KEY_HINT_TEXT:
if len(api_key) > 5:
if results[provider_value]["config"]:
results[provider_value]["config"] += " + api_key"
else:
results[provider_value]["config"] = f"...{api_key[-5:]}"
else:
results[provider_value]["config"] = "...***"
if results[provider_value]["config"]:
results[provider_value]["config"] += " + api_key"
else:
results[provider_value]["config"] = "...***"
else:
# Check environment variables using ProviderKeyManager
env_key_name = ProviderKeyManager.get_env_key_name(provider_value)
env_key_value = os.environ.get(env_key_name)
if env_key_value:
if len(env_key_value) > 5:
results[provider_value]["env"] = f"...{env_key_value[-5:]}"
else:
results[provider_value]["env"] = "...***"

# Check secrets file if it was parsed successfully
if secrets_status == "parsed":
config_key = ProviderKeyManager.get_config_file_key(provider_value, secrets)
if config_key and config_key != API_KEY_HINT_TEXT:
if len(config_key) > 5:
results[provider_value]["config"] = f"...{config_key[-5:]}"
else:
results[provider_value]["config"] = "...***"

return results

Expand Down Expand Up @@ -235,7 +266,7 @@ def show_check_summary() -> None:
system_info = get_system_info()
config_summary = get_config_summary(config_files["config"])
secrets_summary = get_secrets_summary(config_files["secrets"])
api_keys = check_api_keys(secrets_summary)
api_keys = check_api_keys(secrets_summary, config_summary)
fastagent_version = get_fastagent_version()

# System info panel
Expand Down Expand Up @@ -341,8 +372,10 @@ def show_check_summary() -> None:

keys_table.add_row(provider.capitalize(), env_status, config_status, active)

console.print(Panel(keys_table, title="API Keys", border_style="blue"))

# Print the API Keys panel (fix: this was missing)
keys_panel = Panel(keys_table, title="API Keys", border_style="blue", subtitle_align="left")
console.print(keys_panel)

# MCP Servers panel (shown after API Keys)
if config_summary.get("status") == "parsed":
mcp_servers = config_summary.get("mcp_servers", [])
Expand Down Expand Up @@ -447,4 +480,4 @@ def show(
def main(ctx: typer.Context) -> None:
"""Check and diagnose FastAgent configuration."""
if ctx.invoked_subcommand is None:
show_check_summary()
show_check_summary()
17 changes: 17 additions & 0 deletions src/mcp_agent/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,20 @@ class OpenRouterSettings(BaseModel):
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)


class AzureSettings(BaseModel):
"""
Settings for using Azure OpenAI Service in the fast-agent application.
"""

api_key: str | None = None
resource_name: str | None = None
azure_deployment: str | None = None
api_version: str | None = None
base_url: str | None = None # Optional, can be constructed from resource_name

model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)


class OpenTelemetrySettings(BaseModel):
"""
OTEL settings for the fast-agent application.
Expand Down Expand Up @@ -302,6 +316,9 @@ class Settings(BaseSettings):
tensorzero: Optional[TensorZeroSettings] = None
"""Settings for using TensorZero inference gateway"""

azure: AzureSettings | None = None
"""Settings for using Azure OpenAI Service in the fast-agent application"""

logger: LoggerSettings | None = LoggerSettings()
"""Logger settings for the fast-agent application"""

Expand Down
2 changes: 2 additions & 0 deletions src/mcp_agent/llm/model_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from mcp_agent.llm.augmented_llm_playback import PlaybackLLM
from mcp_agent.llm.provider_types import Provider
from mcp_agent.llm.providers.augmented_llm_anthropic import AnthropicAugmentedLLM
from mcp_agent.llm.providers.augmented_llm_azure import AzureOpenAIAugmentedLLM
from mcp_agent.llm.providers.augmented_llm_deepseek import DeepSeekAugmentedLLM
from mcp_agent.llm.providers.augmented_llm_generic import GenericAugmentedLLM
from mcp_agent.llm.providers.augmented_llm_google import GoogleAugmentedLLM
Expand Down Expand Up @@ -113,6 +114,7 @@ class ModelFactory:
Provider.GOOGLE: GoogleAugmentedLLM, # type: ignore
Provider.OPENROUTER: OpenRouterAugmentedLLM,
Provider.TENSORZERO: TensorZeroAugmentedLLM,
Provider.AZURE: AzureOpenAIAugmentedLLM,
}

# Mapping of special model names to their specific LLM classes
Expand Down
1 change: 1 addition & 0 deletions src/mcp_agent/llm/provider_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ class Provider(Enum):
GENERIC = "generic"
OPENROUTER = "openrouter"
TENSORZERO = "tensorzero" # For TensorZero Gateway
AZURE = "azure" # Azure OpenAI Service
Loading
Loading