fast-agent-mcp 0.2.24__py3-none-any.whl → 0.2.25__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fast_agent_mcp-0.2.24.dist-info → fast_agent_mcp-0.2.25.dist-info}/METADATA +7 -4
- {fast_agent_mcp-0.2.24.dist-info → fast_agent_mcp-0.2.25.dist-info}/RECORD +15 -13
- mcp_agent/cli/commands/check_config.py +61 -28
- mcp_agent/config.py +17 -0
- mcp_agent/event_progress.py +1 -0
- mcp_agent/llm/model_factory.py +2 -0
- mcp_agent/llm/provider_types.py +1 -0
- mcp_agent/llm/providers/augmented_llm_azure.py +137 -0
- mcp_agent/llm/providers/augmented_llm_openai.py +5 -6
- mcp_agent/mcp/common.py +16 -0
- mcp_agent/mcp/mcp_aggregator.py +16 -22
- mcp_agent/ui/console_display.py +28 -1
- {fast_agent_mcp-0.2.24.dist-info → fast_agent_mcp-0.2.25.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.24.dist-info → fast_agent_mcp-0.2.25.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.2.24.dist-info → fast_agent_mcp-0.2.25.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: fast-agent-mcp
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.25
|
4
4
|
Summary: Define, Prompt and Test MCP enabled Agents and Workflows
|
5
5
|
Author-email: Shaun Smith <fastagent@llmindset.co.uk>
|
6
6
|
License: Apache License
|
@@ -212,14 +212,15 @@ Requires-Python: >=3.10
|
|
212
212
|
Requires-Dist: a2a-types>=0.1.0
|
213
213
|
Requires-Dist: aiohttp>=3.11.13
|
214
214
|
Requires-Dist: anthropic>=0.49.0
|
215
|
+
Requires-Dist: azure-identity>=1.14.0
|
215
216
|
Requires-Dist: fastapi>=0.115.6
|
216
217
|
Requires-Dist: mcp>=1.8.0
|
217
218
|
Requires-Dist: openai>=1.63.2
|
218
219
|
Requires-Dist: opentelemetry-distro>=0.50b0
|
219
220
|
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.29.0
|
220
|
-
Requires-Dist: opentelemetry-instrumentation-anthropic>=0.39.3
|
221
|
-
Requires-Dist: opentelemetry-instrumentation-mcp>=0.40.3
|
222
|
-
Requires-Dist: opentelemetry-instrumentation-openai>=0.39.3
|
221
|
+
Requires-Dist: opentelemetry-instrumentation-anthropic>=0.39.3; python_version >= '3.10' and python_version < '4.0'
|
222
|
+
Requires-Dist: opentelemetry-instrumentation-mcp>=0.40.3; python_version >= '3.10' and python_version < '4.0'
|
223
|
+
Requires-Dist: opentelemetry-instrumentation-openai>=0.39.3; python_version >= '3.10' and python_version < '4.0'
|
223
224
|
Requires-Dist: prompt-toolkit>=3.0.50
|
224
225
|
Requires-Dist: pydantic-settings>=2.7.0
|
225
226
|
Requires-Dist: pydantic>=2.10.4
|
@@ -227,6 +228,8 @@ Requires-Dist: pyyaml>=6.0.2
|
|
227
228
|
Requires-Dist: rich>=13.9.4
|
228
229
|
Requires-Dist: tensorzero>=2025.4.7
|
229
230
|
Requires-Dist: typer>=0.15.1
|
231
|
+
Provides-Extra: azure
|
232
|
+
Requires-Dist: azure-identity>=1.14.0; extra == 'azure'
|
230
233
|
Provides-Extra: dev
|
231
234
|
Requires-Dist: anthropic>=0.42.0; extra == 'dev'
|
232
235
|
Requires-Dist: pre-commit>=4.0.1; extra == 'dev'
|
@@ -1,10 +1,10 @@
|
|
1
1
|
mcp_agent/__init__.py,sha256=18T0AG0W9sJhTY38O9GFFOzliDhxx9p87CvRyti9zbw,1620
|
2
2
|
mcp_agent/app.py,sha256=WRsiUdwy_9IAnaGRDwuLm7pzgQpt2wgsg10vBOpfcwM,5539
|
3
|
-
mcp_agent/config.py,sha256=
|
3
|
+
mcp_agent/config.py,sha256=0a6P_nvr6fhBLMjk1XsfQuS_wVO1dmQ1sJR5RQdf5FA,13321
|
4
4
|
mcp_agent/console.py,sha256=Gjf2QLFumwG1Lav__c07X_kZxxEUSkzV-1_-YbAwcwo,813
|
5
5
|
mcp_agent/context.py,sha256=5pnw78LgezCLeO5Os5dgmLDadwXqw_B4Ojib48XP1s4,7431
|
6
6
|
mcp_agent/context_dependent.py,sha256=QXfhw3RaQCKfscEEBRGuZ3sdMWqkgShz2jJ1ivGGX1I,1455
|
7
|
-
mcp_agent/event_progress.py,sha256=
|
7
|
+
mcp_agent/event_progress.py,sha256=040lrCCclcOuryi07YGSej25kTQF5_JMXY12Yj-3u1U,2773
|
8
8
|
mcp_agent/mcp_server_registry.py,sha256=QTzu0elBWzqXks6u5nI5n8uN5CX8CpyV6ybxnyt5LZM,11531
|
9
9
|
mcp_agent/progress_display.py,sha256=GeJU9VUt6qKsFVymG688hCMVCsAygG9ifiiEb5IcbN4,361
|
10
10
|
mcp_agent/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -22,7 +22,7 @@ mcp_agent/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
mcp_agent/cli/__main__.py,sha256=AVZ7tQFhU_sDOGuUGJq8ujgKtcxsYJBJwHbVaaiRDlI,166
|
23
23
|
mcp_agent/cli/main.py,sha256=XjrgXMBaPKkVqAFo8T9LJz6Tp1-ivrKDOuNYWke99YA,3090
|
24
24
|
mcp_agent/cli/terminal.py,sha256=GRwD-RGW7saIz2IOWZn5vD6JjiArscELBThm1GTFkuI,1065
|
25
|
-
mcp_agent/cli/commands/check_config.py,sha256=
|
25
|
+
mcp_agent/cli/commands/check_config.py,sha256=KJbXUFx5Qih3lb_r-Fcx_uAjgHhgD7qqPewQtIDofKM,18321
|
26
26
|
mcp_agent/cli/commands/go.py,sha256=LIsOJQuTdfCUcNm7JT-NQDU8cI-GCnYwYjN2VOWxvqs,8658
|
27
27
|
mcp_agent/cli/commands/quickstart.py,sha256=SM3CHMzDgvTxIpKjFuX9BrS_N1vRoXNBDaO90aWx1Rk,14586
|
28
28
|
mcp_agent/cli/commands/setup.py,sha256=eOEd4TL-b0DaDeSJMGOfNOsTEItoZ67W88eTP4aP-bo,6482
|
@@ -53,19 +53,20 @@ mcp_agent/llm/augmented_llm.py,sha256=CqtSGo_QrHE73tz_DHMd0wdt2F41gwuUu5Bue51FNm
|
|
53
53
|
mcp_agent/llm/augmented_llm_passthrough.py,sha256=zHcctNpwg4EFJvD1x9Eg443SVX-uyzFphLikwF_yVE0,6288
|
54
54
|
mcp_agent/llm/augmented_llm_playback.py,sha256=6L_RWIK__R67oZK7u3Xt3hWy1T2LnHXIO-efqgP3tPw,4177
|
55
55
|
mcp_agent/llm/memory.py,sha256=HQ_c1QemOUjrkY6Z2omE6BG5fXga7y4jN7KCMOuGjPs,3345
|
56
|
-
mcp_agent/llm/model_factory.py,sha256=
|
56
|
+
mcp_agent/llm/model_factory.py,sha256=vKR4wI0cDElMc4JSE-WuLXTarAZsi9-5I7B9wUbI6c4,8218
|
57
57
|
mcp_agent/llm/prompt_utils.py,sha256=yWQHykoK13QRF7evHUKxVF0SpVLN-Bsft0Yixzvn0g0,4825
|
58
58
|
mcp_agent/llm/provider_key_manager.py,sha256=-K_FuibN6hdSnweT32lB8mKTfCARnbja6zYYs0ErTKg,2802
|
59
|
-
mcp_agent/llm/provider_types.py,sha256=
|
59
|
+
mcp_agent/llm/provider_types.py,sha256=m7vAQA0MSn4iVCoHQYwZ8pK8nW4iVLxp_Ul1JpnXMpY,408
|
60
60
|
mcp_agent/llm/sampling_converter.py,sha256=C7wPBlmT0eD90XWabC22zkxsrVHKCrjwIwg6cG628cI,2926
|
61
61
|
mcp_agent/llm/sampling_format_converter.py,sha256=xGz4odHpOcP7--eFaJaFtUR8eR9jxZS7MnLH6J7n0EU,1263
|
62
62
|
mcp_agent/llm/providers/__init__.py,sha256=heVxtmuqFJOnjjxHz4bWSqTAxXoN1E8twC_gQ_yJpHk,265
|
63
63
|
mcp_agent/llm/providers/anthropic_utils.py,sha256=vYDN5G5jKMhD2CQg8veJYab7tvvzYkDMq8M1g_hUAQg,3275
|
64
64
|
mcp_agent/llm/providers/augmented_llm_anthropic.py,sha256=gK_IvllVBNJUUrSfpgFpdhM-d4liCt0MLq7d2lXS7RI,15510
|
65
|
+
mcp_agent/llm/providers/augmented_llm_azure.py,sha256=VPrD6lNrEw6EdYUTa9MDvHDNIPjJU5CG5xnKCM3JYdA,5878
|
65
66
|
mcp_agent/llm/providers/augmented_llm_deepseek.py,sha256=NiZK5nv91ZS2VgVFXpbsFNFYLsLcppcbo_RstlRMd7I,1145
|
66
67
|
mcp_agent/llm/providers/augmented_llm_generic.py,sha256=5Uq8ZBhcFuQTt7koP_5ykolREh2iWu8zKhNbh3pM9lQ,1210
|
67
68
|
mcp_agent/llm/providers/augmented_llm_google.py,sha256=N0a2fphVtkvNYxKQpEX6J4tlO1C_mRw4sw3LBXnrOeI,1130
|
68
|
-
mcp_agent/llm/providers/augmented_llm_openai.py,sha256=
|
69
|
+
mcp_agent/llm/providers/augmented_llm_openai.py,sha256=5CFHKayjm-aeCBpohIK3WelAEuX7_LDGZIKnWR_rq-s,14577
|
69
70
|
mcp_agent/llm/providers/augmented_llm_openrouter.py,sha256=V_TlVKm92GHBxYIo6gpvH_6cAaIdppS25Tz6x5T7LW0,2341
|
70
71
|
mcp_agent/llm/providers/augmented_llm_tensorzero.py,sha256=Mol_Wzj_ZtccW-LMw0oFwWUt1m1yfofloay9QYNP23c,20729
|
71
72
|
mcp_agent/llm/providers/multipart_converter_anthropic.py,sha256=t5lHYGfFUacJldnrVtMNW-8gEMoto8Y7hJkDrnyZR-Y,16650
|
@@ -83,11 +84,12 @@ mcp_agent/logging/logger.py,sha256=l02OGX_c5FOyH0rspd4ZvnkJcbb0FahhUhlh2KI8mqE,1
|
|
83
84
|
mcp_agent/logging/rich_progress.py,sha256=oY9fjb4Tyw6887v8sgO6EGIK4lnmIoR3NNxhA_-Ln_M,4893
|
84
85
|
mcp_agent/logging/transport.py,sha256=m8YsLLu5T8eof_ndpLQs4gHOzqqEL98xsVwBwDsBfxI,17335
|
85
86
|
mcp_agent/mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
87
|
+
mcp_agent/mcp/common.py,sha256=DiWLH9rxWvCgkKRsHQehY9mDhQl9gki1-q7LVUflDvI,425
|
86
88
|
mcp_agent/mcp/gen_client.py,sha256=fAVwFVCgSamw4PwoWOV4wrK9TABx1S_zZv8BctRyF2k,3030
|
87
89
|
mcp_agent/mcp/interfaces.py,sha256=PAou8znAl2HgtvfCpLQOZFbKra9F72OcVRfBJbboNX8,6965
|
88
90
|
mcp_agent/mcp/logger_textio.py,sha256=vljC1BtNTCxBAda9ExqNB-FwVNUZIuJT3h1nWmCjMws,3172
|
89
91
|
mcp_agent/mcp/mcp_agent_client_session.py,sha256=4597ww1ihSKh-zKc9xMF3ODqosVPU_A4xVmUbk1DvcE,6002
|
90
|
-
mcp_agent/mcp/mcp_aggregator.py,sha256=
|
92
|
+
mcp_agent/mcp/mcp_aggregator.py,sha256=KAtQTg6CBpTiHoMg6NKcMSiJ7Cvl-BgS0Lff784qwrs,46063
|
91
93
|
mcp_agent/mcp/mcp_connection_manager.py,sha256=jlqaAdS4zc1UfVBHQU0TkTbVr0-rOkbN9bkrLPrZVLk,17159
|
92
94
|
mcp_agent/mcp/mime_utils.py,sha256=difepNR_gpb4MpMLkBRAoyhDk-AjXUHTiqKvT_VwS1o,1805
|
93
95
|
mcp_agent/mcp/prompt_message_multipart.py,sha256=BDwRdNwyWHb2q2bccDb2iR2VlORqVvkvoG3xYzcMpCE,4403
|
@@ -145,9 +147,9 @@ mcp_agent/resources/examples/workflows/orchestrator.py,sha256=rOGilFTliWWnZ3Jx5w
|
|
145
147
|
mcp_agent/resources/examples/workflows/parallel.py,sha256=DQ5vY5-h8Qa5QHcYjsWXhZ_FYrYoloVWOdgeXV9p2gI,1890
|
146
148
|
mcp_agent/resources/examples/workflows/router.py,sha256=E4x_-c3l4YW9w1i4ARcDtkdeqIdbWEGfsMzwLYpdbVc,1677
|
147
149
|
mcp_agent/resources/examples/workflows/short_story.txt,sha256=X3y_1AyhLFN2AKzCKvucJtDgAFIJfnlbsbGZO5bBWu0,1187
|
148
|
-
mcp_agent/ui/console_display.py,sha256=
|
149
|
-
fast_agent_mcp-0.2.
|
150
|
-
fast_agent_mcp-0.2.
|
151
|
-
fast_agent_mcp-0.2.
|
152
|
-
fast_agent_mcp-0.2.
|
153
|
-
fast_agent_mcp-0.2.
|
150
|
+
mcp_agent/ui/console_display.py,sha256=UKqax5V2TC0hkZZORmmd6UqUk0DGX7A25E3h1k9f42k,10982
|
151
|
+
fast_agent_mcp-0.2.25.dist-info/METADATA,sha256=V-sL9gUnTgSUvsV2plQm3Spj4nJAnKYjermxRjIPCxc,30488
|
152
|
+
fast_agent_mcp-0.2.25.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
153
|
+
fast_agent_mcp-0.2.25.dist-info/entry_points.txt,sha256=bRniFM5zk3Kix5z7scX0gf9VnmGQ2Cz_Q1Gh7Ir4W00,186
|
154
|
+
fast_agent_mcp-0.2.25.dist-info/licenses/LICENSE,sha256=cN3FxDURL9XuzE5mhK9L2paZo82LTfjwCYVT7e3j0e4,10939
|
155
|
+
fast_agent_mcp-0.2.25.dist-info/RECORD,,
|
@@ -92,42 +92,73 @@ def get_secrets_summary(secrets_path: Optional[Path]) -> dict:
|
|
92
92
|
return result
|
93
93
|
|
94
94
|
|
95
|
-
def check_api_keys(secrets_summary: dict) -> dict:
|
96
|
-
"""Check if API keys are configured in secrets file or environment.
|
95
|
+
def check_api_keys(secrets_summary: dict, config_summary: dict) -> dict:
|
96
|
+
"""Check if API keys are configured in secrets file or environment, including Azure DefaultAzureCredential.
|
97
|
+
Now also checks Azure config in main config file for retrocompatibility.
|
98
|
+
"""
|
97
99
|
import os
|
98
100
|
|
99
|
-
# Initialize results dict using Provider enum values
|
100
101
|
results = {
|
101
|
-
provider.value: {"env":
|
102
|
+
provider.value: {"env": "", "config": ""}
|
102
103
|
for provider in Provider
|
103
104
|
if provider != Provider.FAST_AGENT
|
104
|
-
}
|
105
|
+
}
|
105
106
|
|
106
107
|
# Get secrets if available
|
107
108
|
secrets = secrets_summary.get("secrets", {})
|
108
109
|
secrets_status = secrets_summary.get("status", "not_found")
|
110
|
+
# Get config if available
|
111
|
+
config = config_summary if config_summary.get("status") == "parsed" else {}
|
112
|
+
config_azure = {}
|
113
|
+
if config and "azure" in config.get("config", {}):
|
114
|
+
config_azure = config["config"]["azure"]
|
109
115
|
|
110
|
-
# Check both environment variables and config file for each provider
|
111
116
|
for provider_value in results:
|
112
|
-
#
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
if
|
126
|
-
|
127
|
-
|
128
|
-
|
117
|
+
# Special handling for Azure: support api_key and DefaultAzureCredential
|
118
|
+
if provider_value == "azure":
|
119
|
+
# Prefer secrets if present, else fallback to config
|
120
|
+
azure_cfg = {}
|
121
|
+
if secrets_status == "parsed" and "azure" in secrets:
|
122
|
+
azure_cfg = secrets.get("azure", {})
|
123
|
+
elif config_azure:
|
124
|
+
azure_cfg = config_azure
|
125
|
+
|
126
|
+
use_default_cred = azure_cfg.get("use_default_azure_credential", False)
|
127
|
+
base_url = azure_cfg.get("base_url")
|
128
|
+
api_key = azure_cfg.get("api_key")
|
129
|
+
# DefaultAzureCredential mode
|
130
|
+
if use_default_cred and base_url:
|
131
|
+
results[provider_value]["config"] = "DefaultAzureCredential"
|
132
|
+
# API key mode (retrocompatible)
|
133
|
+
if api_key and api_key != API_KEY_HINT_TEXT:
|
134
|
+
if len(api_key) > 5:
|
135
|
+
if results[provider_value]["config"]:
|
136
|
+
results[provider_value]["config"] += " + api_key"
|
137
|
+
else:
|
138
|
+
results[provider_value]["config"] = f"...{api_key[-5:]}"
|
129
139
|
else:
|
130
|
-
results[provider_value]["config"]
|
140
|
+
if results[provider_value]["config"]:
|
141
|
+
results[provider_value]["config"] += " + api_key"
|
142
|
+
else:
|
143
|
+
results[provider_value]["config"] = "...***"
|
144
|
+
else:
|
145
|
+
# Check environment variables using ProviderKeyManager
|
146
|
+
env_key_name = ProviderKeyManager.get_env_key_name(provider_value)
|
147
|
+
env_key_value = os.environ.get(env_key_name)
|
148
|
+
if env_key_value:
|
149
|
+
if len(env_key_value) > 5:
|
150
|
+
results[provider_value]["env"] = f"...{env_key_value[-5:]}"
|
151
|
+
else:
|
152
|
+
results[provider_value]["env"] = "...***"
|
153
|
+
|
154
|
+
# Check secrets file if it was parsed successfully
|
155
|
+
if secrets_status == "parsed":
|
156
|
+
config_key = ProviderKeyManager.get_config_file_key(provider_value, secrets)
|
157
|
+
if config_key and config_key != API_KEY_HINT_TEXT:
|
158
|
+
if len(config_key) > 5:
|
159
|
+
results[provider_value]["config"] = f"...{config_key[-5:]}"
|
160
|
+
else:
|
161
|
+
results[provider_value]["config"] = "...***"
|
131
162
|
|
132
163
|
return results
|
133
164
|
|
@@ -235,7 +266,7 @@ def show_check_summary() -> None:
|
|
235
266
|
system_info = get_system_info()
|
236
267
|
config_summary = get_config_summary(config_files["config"])
|
237
268
|
secrets_summary = get_secrets_summary(config_files["secrets"])
|
238
|
-
api_keys = check_api_keys(secrets_summary)
|
269
|
+
api_keys = check_api_keys(secrets_summary, config_summary)
|
239
270
|
fastagent_version = get_fastagent_version()
|
240
271
|
|
241
272
|
# System info panel
|
@@ -341,8 +372,10 @@ def show_check_summary() -> None:
|
|
341
372
|
|
342
373
|
keys_table.add_row(provider.capitalize(), env_status, config_status, active)
|
343
374
|
|
344
|
-
|
345
|
-
|
375
|
+
# Print the API Keys panel (fix: this was missing)
|
376
|
+
keys_panel = Panel(keys_table, title="API Keys", border_style="blue", subtitle_align="left")
|
377
|
+
console.print(keys_panel)
|
378
|
+
|
346
379
|
# MCP Servers panel (shown after API Keys)
|
347
380
|
if config_summary.get("status") == "parsed":
|
348
381
|
mcp_servers = config_summary.get("mcp_servers", [])
|
@@ -447,4 +480,4 @@ def show(
|
|
447
480
|
def main(ctx: typer.Context) -> None:
|
448
481
|
"""Check and diagnose FastAgent configuration."""
|
449
482
|
if ctx.invoked_subcommand is None:
|
450
|
-
show_check_summary()
|
483
|
+
show_check_summary()
|
mcp_agent/config.py
CHANGED
@@ -179,6 +179,20 @@ class OpenRouterSettings(BaseModel):
|
|
179
179
|
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
180
180
|
|
181
181
|
|
182
|
+
class AzureSettings(BaseModel):
|
183
|
+
"""
|
184
|
+
Settings for using Azure OpenAI Service in the fast-agent application.
|
185
|
+
"""
|
186
|
+
|
187
|
+
api_key: str | None = None
|
188
|
+
resource_name: str | None = None
|
189
|
+
azure_deployment: str | None = None
|
190
|
+
api_version: str | None = None
|
191
|
+
base_url: str | None = None # Optional, can be constructed from resource_name
|
192
|
+
|
193
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
194
|
+
|
195
|
+
|
182
196
|
class OpenTelemetrySettings(BaseModel):
|
183
197
|
"""
|
184
198
|
OTEL settings for the fast-agent application.
|
@@ -302,6 +316,9 @@ class Settings(BaseSettings):
|
|
302
316
|
tensorzero: Optional[TensorZeroSettings] = None
|
303
317
|
"""Settings for using TensorZero inference gateway"""
|
304
318
|
|
319
|
+
azure: AzureSettings | None = None
|
320
|
+
"""Settings for using Azure OpenAI Service in the fast-agent application"""
|
321
|
+
|
305
322
|
logger: LoggerSettings | None = LoggerSettings()
|
306
323
|
"""Logger settings for the fast-agent application"""
|
307
324
|
|
mcp_agent/event_progress.py
CHANGED
mcp_agent/llm/model_factory.py
CHANGED
@@ -10,6 +10,7 @@ from mcp_agent.llm.augmented_llm_passthrough import PassthroughLLM
|
|
10
10
|
from mcp_agent.llm.augmented_llm_playback import PlaybackLLM
|
11
11
|
from mcp_agent.llm.provider_types import Provider
|
12
12
|
from mcp_agent.llm.providers.augmented_llm_anthropic import AnthropicAugmentedLLM
|
13
|
+
from mcp_agent.llm.providers.augmented_llm_azure import AzureOpenAIAugmentedLLM
|
13
14
|
from mcp_agent.llm.providers.augmented_llm_deepseek import DeepSeekAugmentedLLM
|
14
15
|
from mcp_agent.llm.providers.augmented_llm_generic import GenericAugmentedLLM
|
15
16
|
from mcp_agent.llm.providers.augmented_llm_google import GoogleAugmentedLLM
|
@@ -113,6 +114,7 @@ class ModelFactory:
|
|
113
114
|
Provider.GOOGLE: GoogleAugmentedLLM, # type: ignore
|
114
115
|
Provider.OPENROUTER: OpenRouterAugmentedLLM,
|
115
116
|
Provider.TENSORZERO: TensorZeroAugmentedLLM,
|
117
|
+
Provider.AZURE: AzureOpenAIAugmentedLLM,
|
116
118
|
}
|
117
119
|
|
118
120
|
# Mapping of special model names to their specific LLM classes
|
mcp_agent/llm/provider_types.py
CHANGED
@@ -0,0 +1,137 @@
|
|
1
|
+
from openai import AuthenticationError, AzureOpenAI, OpenAI
|
2
|
+
|
3
|
+
from mcp_agent.core.exceptions import ProviderKeyError
|
4
|
+
from mcp_agent.llm.provider_types import Provider
|
5
|
+
from mcp_agent.llm.providers.augmented_llm_openai import OpenAIAugmentedLLM
|
6
|
+
|
7
|
+
try:
|
8
|
+
from azure.identity import DefaultAzureCredential
|
9
|
+
except ImportError:
|
10
|
+
DefaultAzureCredential = None
|
11
|
+
|
12
|
+
|
13
|
+
def _extract_resource_name(url: str) -> str | None:
|
14
|
+
from urllib.parse import urlparse
|
15
|
+
|
16
|
+
host = urlparse(url).hostname or ""
|
17
|
+
suffix = ".openai.azure.com"
|
18
|
+
return host.replace(suffix, "") if host.endswith(suffix) else None
|
19
|
+
|
20
|
+
|
21
|
+
DEFAULT_AZURE_API_VERSION = "2023-05-15"
|
22
|
+
|
23
|
+
|
24
|
+
class AzureOpenAIAugmentedLLM(OpenAIAugmentedLLM):
|
25
|
+
"""
|
26
|
+
Azure OpenAI implementation extending OpenAIAugmentedLLM.
|
27
|
+
Handles both API Key and DefaultAzureCredential authentication.
|
28
|
+
"""
|
29
|
+
|
30
|
+
def __init__(self, provider: Provider = Provider.AZURE, *args, **kwargs):
|
31
|
+
# Set provider to AZURE, pass through to base
|
32
|
+
super().__init__(provider=provider, *args, **kwargs)
|
33
|
+
|
34
|
+
# Context/config extraction
|
35
|
+
context = getattr(self, "context", None)
|
36
|
+
config = getattr(context, "config", None) if context else None
|
37
|
+
azure_cfg = getattr(config, "azure", None) if config else None
|
38
|
+
|
39
|
+
if azure_cfg is None:
|
40
|
+
raise ProviderKeyError(
|
41
|
+
"Missing Azure configuration",
|
42
|
+
"Azure provider requires configuration section 'azure' in your config file.",
|
43
|
+
)
|
44
|
+
|
45
|
+
self.use_default_cred = getattr(azure_cfg, "use_default_azure_credential", False)
|
46
|
+
default_request_params = getattr(self, "default_request_params", None)
|
47
|
+
self.deployment_name = getattr(default_request_params, "model", None) or getattr(
|
48
|
+
azure_cfg, "azure_deployment", None
|
49
|
+
)
|
50
|
+
self.api_version = getattr(azure_cfg, "api_version", None) or DEFAULT_AZURE_API_VERSION
|
51
|
+
|
52
|
+
if self.use_default_cred:
|
53
|
+
self.base_url = getattr(azure_cfg, "base_url", None)
|
54
|
+
if not self.base_url:
|
55
|
+
raise ProviderKeyError(
|
56
|
+
"Missing Azure endpoint",
|
57
|
+
"When using 'use_default_azure_credential', 'base_url' is required in azure config.",
|
58
|
+
)
|
59
|
+
if DefaultAzureCredential is None:
|
60
|
+
raise ProviderKeyError(
|
61
|
+
"azure-identity not installed",
|
62
|
+
"You must install 'azure-identity' to use DefaultAzureCredential authentication.",
|
63
|
+
)
|
64
|
+
self.credential = DefaultAzureCredential()
|
65
|
+
|
66
|
+
def get_azure_token():
|
67
|
+
token = self.credential.get_token("https://cognitiveservices.azure.com/.default")
|
68
|
+
return token.token
|
69
|
+
|
70
|
+
self.get_azure_token = get_azure_token
|
71
|
+
else:
|
72
|
+
self.api_key = getattr(azure_cfg, "api_key", None)
|
73
|
+
self.resource_name = getattr(azure_cfg, "resource_name", None)
|
74
|
+
self.base_url = getattr(azure_cfg, "base_url", None) or (
|
75
|
+
f"https://{self.resource_name}.openai.azure.com/" if self.resource_name else None
|
76
|
+
)
|
77
|
+
if not self.api_key:
|
78
|
+
raise ProviderKeyError(
|
79
|
+
"Missing Azure OpenAI credentials",
|
80
|
+
"Field 'api_key' is required in azure config.",
|
81
|
+
)
|
82
|
+
if not (self.resource_name or self.base_url):
|
83
|
+
raise ProviderKeyError(
|
84
|
+
"Missing Azure endpoint",
|
85
|
+
"Provide either 'resource_name' or 'base_url' under azure config.",
|
86
|
+
)
|
87
|
+
if not self.deployment_name:
|
88
|
+
raise ProviderKeyError(
|
89
|
+
"Missing deployment name",
|
90
|
+
"Set 'azure_deployment' in config or pass model=<deployment>.",
|
91
|
+
)
|
92
|
+
# If resource_name was missing, try to extract it from base_url
|
93
|
+
if not self.resource_name and self.base_url:
|
94
|
+
self.resource_name = _extract_resource_name(self.base_url)
|
95
|
+
|
96
|
+
def _openai_client(self) -> OpenAI:
|
97
|
+
"""
|
98
|
+
Returns an AzureOpenAI client, handling both API Key and DefaultAzureCredential.
|
99
|
+
"""
|
100
|
+
try:
|
101
|
+
if self.use_default_cred:
|
102
|
+
if self.base_url is None:
|
103
|
+
raise ProviderKeyError(
|
104
|
+
"Missing Azure endpoint",
|
105
|
+
"azure_endpoint (base_url) is None at client creation time.",
|
106
|
+
)
|
107
|
+
return AzureOpenAI(
|
108
|
+
azure_ad_token_provider=self.get_azure_token,
|
109
|
+
azure_endpoint=self.base_url,
|
110
|
+
api_version=self.api_version,
|
111
|
+
azure_deployment=self.deployment_name,
|
112
|
+
)
|
113
|
+
else:
|
114
|
+
if self.base_url is None:
|
115
|
+
raise ProviderKeyError(
|
116
|
+
"Missing Azure endpoint",
|
117
|
+
"azure_endpoint (base_url) is None at client creation time.",
|
118
|
+
)
|
119
|
+
return AzureOpenAI(
|
120
|
+
api_key=self.api_key,
|
121
|
+
azure_endpoint=self.base_url,
|
122
|
+
api_version=self.api_version,
|
123
|
+
azure_deployment=self.deployment_name,
|
124
|
+
)
|
125
|
+
except AuthenticationError as e:
|
126
|
+
if self.use_default_cred:
|
127
|
+
raise ProviderKeyError(
|
128
|
+
"Invalid Azure AD credentials",
|
129
|
+
"The configured Azure AD credentials were rejected.\n"
|
130
|
+
"Please check your Azure identity setup.",
|
131
|
+
) from e
|
132
|
+
else:
|
133
|
+
raise ProviderKeyError(
|
134
|
+
"Invalid Azure OpenAI API key",
|
135
|
+
"The configured Azure OpenAI API key was rejected.\n"
|
136
|
+
"Please check that your API key is valid and not expired.",
|
137
|
+
) from e
|
@@ -78,7 +78,7 @@ class OpenAIAugmentedLLM(AugmentedLLM[ChatCompletionMessageParam, ChatCompletion
|
|
78
78
|
self._reasoning_effort = self.context.config.openai.reasoning_effort
|
79
79
|
|
80
80
|
# Determine if we're using a reasoning model
|
81
|
-
# TODO -- move this to model
|
81
|
+
# TODO -- move this to model capabilities, add o4.
|
82
82
|
chosen_model = self.default_request_params.model if self.default_request_params else None
|
83
83
|
self._reasoning = chosen_model and (
|
84
84
|
chosen_model.startswith("o3") or chosen_model.startswith("o1")
|
@@ -325,7 +325,7 @@ class OpenAIAugmentedLLM(AugmentedLLM[ChatCompletionMessageParam, ChatCompletion
|
|
325
325
|
return result
|
326
326
|
|
327
327
|
def _prepare_api_request(
|
328
|
-
self, messages, tools, request_params: RequestParams
|
328
|
+
self, messages, tools: List[ChatCompletionToolParam] | None, request_params: RequestParams
|
329
329
|
) -> dict[str, str]:
|
330
330
|
# Create base arguments dictionary
|
331
331
|
|
@@ -345,9 +345,8 @@ class OpenAIAugmentedLLM(AugmentedLLM[ChatCompletionMessageParam, ChatCompletion
|
|
345
345
|
)
|
346
346
|
else:
|
347
347
|
base_args["max_tokens"] = request_params.maxTokens
|
348
|
-
|
349
|
-
|
350
|
-
base_args["parallel_tool_calls"] = request_params.parallel_tool_calls
|
348
|
+
if tools:
|
349
|
+
base_args["parallel_tool_calls"] = request_params.parallel_tool_calls
|
351
350
|
|
352
351
|
arguments: Dict[str, str] = self.prepare_provider_arguments(
|
353
352
|
base_args, request_params, self.OPENAI_EXCLUDE_FIELDS.union(self.BASE_EXCLUDE_FIELDS)
|
@@ -356,7 +355,7 @@ class OpenAIAugmentedLLM(AugmentedLLM[ChatCompletionMessageParam, ChatCompletion
|
|
356
355
|
|
357
356
|
def adjust_schema(self, inputSchema: Dict) -> Dict:
|
358
357
|
# return inputSchema
|
359
|
-
if not Provider.OPENAI
|
358
|
+
if self.provider not in [Provider.OPENAI, Provider.AZURE]:
|
360
359
|
return inputSchema
|
361
360
|
|
362
361
|
if "properties" in inputSchema:
|
mcp_agent/mcp/common.py
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
"""
|
2
|
+
Common constants and utilities shared between modules to avoid circular imports.
|
3
|
+
"""
|
4
|
+
|
5
|
+
# Constants
|
6
|
+
SEP = "-"
|
7
|
+
|
8
|
+
|
9
|
+
def create_namespaced_name(server_name: str, resource_name: str) -> str:
|
10
|
+
"""Create a namespaced resource name from server and resource names"""
|
11
|
+
return f"{server_name}{SEP}{resource_name}"
|
12
|
+
|
13
|
+
|
14
|
+
def is_namespaced_name(name: str) -> bool:
|
15
|
+
"""Check if a name is already namespaced"""
|
16
|
+
return SEP in name
|
mcp_agent/mcp/mcp_aggregator.py
CHANGED
@@ -25,6 +25,7 @@ from pydantic import AnyUrl, BaseModel, ConfigDict
|
|
25
25
|
from mcp_agent.context_dependent import ContextDependent
|
26
26
|
from mcp_agent.event_progress import ProgressAction
|
27
27
|
from mcp_agent.logging.logger import get_logger
|
28
|
+
from mcp_agent.mcp.common import SEP, create_namespaced_name, is_namespaced_name
|
28
29
|
from mcp_agent.mcp.gen_client import gen_client
|
29
30
|
from mcp_agent.mcp.mcp_agent_client_session import MCPAgentClientSession
|
30
31
|
from mcp_agent.mcp.mcp_connection_manager import MCPConnectionManager
|
@@ -35,23 +36,11 @@ if TYPE_CHECKING:
|
|
35
36
|
|
36
37
|
logger = get_logger(__name__) # This will be replaced per-instance when agent_name is available
|
37
38
|
|
38
|
-
SEP = "-"
|
39
|
-
|
40
39
|
# Define type variables for the generalized method
|
41
40
|
T = TypeVar("T")
|
42
41
|
R = TypeVar("R")
|
43
42
|
|
44
43
|
|
45
|
-
def create_namespaced_name(server_name: str, resource_name: str) -> str:
|
46
|
-
"""Create a namespaced resource name from server and resource names"""
|
47
|
-
return f"{server_name}{SEP}{resource_name}"
|
48
|
-
|
49
|
-
|
50
|
-
def is_namespaced_name(name: str) -> bool:
|
51
|
-
"""Check if a name is already namespaced"""
|
52
|
-
return SEP in name
|
53
|
-
|
54
|
-
|
55
44
|
class NamespacedTool(BaseModel):
|
56
45
|
"""
|
57
46
|
A tool that is namespaced by server name.
|
@@ -94,6 +83,11 @@ class MCPAggregator(ContextDependent):
|
|
94
83
|
self._persistent_connection_manager = self.context._connection_manager
|
95
84
|
|
96
85
|
await self.load_servers()
|
86
|
+
# Import the display component here to avoid circular imports
|
87
|
+
from mcp_agent.ui.console_display import ConsoleDisplay
|
88
|
+
|
89
|
+
# Initialize the display component
|
90
|
+
self.display = ConsoleDisplay(config=self.context.config)
|
97
91
|
|
98
92
|
return self
|
99
93
|
|
@@ -227,12 +221,11 @@ class MCPAggregator(ContextDependent):
|
|
227
221
|
write_stream,
|
228
222
|
read_timeout,
|
229
223
|
server_name=server_name,
|
230
|
-
tool_list_changed_callback=self._handle_tool_list_changed
|
224
|
+
tool_list_changed_callback=self._handle_tool_list_changed,
|
231
225
|
)
|
232
226
|
|
233
227
|
await self._persistent_connection_manager.get_server(
|
234
|
-
server_name,
|
235
|
-
client_session_factory=session_factory
|
228
|
+
server_name, client_session_factory=session_factory
|
236
229
|
)
|
237
230
|
|
238
231
|
logger.info(
|
@@ -282,13 +275,13 @@ class MCPAggregator(ContextDependent):
|
|
282
275
|
write_stream,
|
283
276
|
read_timeout,
|
284
277
|
server_name=server_name,
|
285
|
-
tool_list_changed_callback=self._handle_tool_list_changed
|
278
|
+
tool_list_changed_callback=self._handle_tool_list_changed,
|
286
279
|
)
|
287
280
|
|
288
281
|
async with gen_client(
|
289
282
|
server_name,
|
290
283
|
server_registry=self.context.server_registry,
|
291
|
-
client_session_factory=create_session
|
284
|
+
client_session_factory=create_session,
|
292
285
|
) as client:
|
293
286
|
tools = await fetch_tools(client)
|
294
287
|
prompts = await fetch_prompts(client, server_name)
|
@@ -923,6 +916,8 @@ class MCPAggregator(ContextDependent):
|
|
923
916
|
logger.error(f"Cannot refresh tools for unknown server '{server_name}'")
|
924
917
|
return
|
925
918
|
|
919
|
+
await self.display.show_tool_update(aggregator=self, updated_server=server_name)
|
920
|
+
|
926
921
|
async with self._refresh_lock:
|
927
922
|
try:
|
928
923
|
# Fetch new tools from the server
|
@@ -934,12 +929,11 @@ class MCPAggregator(ContextDependent):
|
|
934
929
|
write_stream,
|
935
930
|
read_timeout,
|
936
931
|
server_name=server_name,
|
937
|
-
tool_list_changed_callback=self._handle_tool_list_changed
|
932
|
+
tool_list_changed_callback=self._handle_tool_list_changed,
|
938
933
|
)
|
939
934
|
|
940
935
|
server_connection = await self._persistent_connection_manager.get_server(
|
941
|
-
server_name,
|
942
|
-
client_session_factory=create_session
|
936
|
+
server_name, client_session_factory=create_session
|
943
937
|
)
|
944
938
|
tools_result = await server_connection.session.list_tools()
|
945
939
|
new_tools = tools_result.tools or []
|
@@ -951,13 +945,13 @@ class MCPAggregator(ContextDependent):
|
|
951
945
|
write_stream,
|
952
946
|
read_timeout,
|
953
947
|
server_name=server_name,
|
954
|
-
tool_list_changed_callback=self._handle_tool_list_changed
|
948
|
+
tool_list_changed_callback=self._handle_tool_list_changed,
|
955
949
|
)
|
956
950
|
|
957
951
|
async with gen_client(
|
958
952
|
server_name,
|
959
953
|
server_registry=self.context.server_registry,
|
960
|
-
client_session_factory=create_session
|
954
|
+
client_session_factory=create_session,
|
961
955
|
) as client:
|
962
956
|
tools_result = await client.list_tools()
|
963
957
|
new_tools = tools_result.tools or []
|
mcp_agent/ui/console_display.py
CHANGED
@@ -5,7 +5,8 @@ from rich.panel import Panel
|
|
5
5
|
from rich.text import Text
|
6
6
|
|
7
7
|
from mcp_agent import console
|
8
|
-
from mcp_agent.mcp.
|
8
|
+
from mcp_agent.mcp.common import SEP
|
9
|
+
from mcp_agent.mcp.mcp_aggregator import MCPAggregator
|
9
10
|
|
10
11
|
# Constants
|
11
12
|
HUMAN_INPUT_TOOL_NAME = "__human_input__"
|
@@ -96,6 +97,32 @@ class ConsoleDisplay:
|
|
96
97
|
console.console.print(panel, markup=self._markup)
|
97
98
|
console.console.print("\n")
|
98
99
|
|
100
|
+
async def show_tool_update(self, aggregator: MCPAggregator | None, updated_server: str) -> None:
|
101
|
+
"""Show a tool update for a server"""
|
102
|
+
if not self.config or not self.config.logger.show_tools:
|
103
|
+
return
|
104
|
+
|
105
|
+
display_server_list = Text()
|
106
|
+
|
107
|
+
if aggregator:
|
108
|
+
for server_name in await aggregator.list_servers():
|
109
|
+
style = "green" if updated_server == server_name else "dim white"
|
110
|
+
display_server_list.append(f"[{server_name}] ", style)
|
111
|
+
|
112
|
+
panel = Panel(
|
113
|
+
f"[dim green]Updating tools for server {updated_server}[/]",
|
114
|
+
title="[TOOL UPDATE]",
|
115
|
+
title_align="left",
|
116
|
+
style="green",
|
117
|
+
border_style="bold white",
|
118
|
+
padding=(1, 2),
|
119
|
+
subtitle=display_server_list,
|
120
|
+
subtitle_align="left",
|
121
|
+
)
|
122
|
+
console.console.print("\n")
|
123
|
+
console.console.print(panel, markup=self._markup)
|
124
|
+
console.console.print("\n")
|
125
|
+
|
99
126
|
def _format_tool_list(self, available_tools, selected_tool_name):
|
100
127
|
"""Format the list of available tools, highlighting the selected one."""
|
101
128
|
display_tool_list = Text()
|
File without changes
|
File without changes
|
File without changes
|