fast-agent-mcp 0.2.28__py3-none-any.whl → 0.2.29__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fast-agent-mcp
3
- Version: 0.2.28
3
+ Version: 0.2.29
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
@@ -213,16 +213,17 @@ Requires-Dist: a2a-types>=0.1.0
213
213
  Requires-Dist: aiohttp>=3.11.13
214
214
  Requires-Dist: anthropic>=0.49.0
215
215
  Requires-Dist: azure-identity>=1.14.0
216
+ Requires-Dist: deprecated>=1.2.18
216
217
  Requires-Dist: fastapi>=0.115.6
217
218
  Requires-Dist: google-genai
218
- Requires-Dist: mcp==1.9.1
219
+ Requires-Dist: mcp==1.9.3
219
220
  Requires-Dist: openai>=1.63.2
220
221
  Requires-Dist: opentelemetry-distro>=0.50b0
221
222
  Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.29.0
222
- Requires-Dist: opentelemetry-instrumentation-anthropic>=0.39.3; python_version >= '3.10' and python_version < '4.0'
223
+ Requires-Dist: opentelemetry-instrumentation-anthropic>=0.40.7; python_version >= '3.10' and python_version < '4.0'
223
224
  Requires-Dist: opentelemetry-instrumentation-google-genai>=0.2b0
224
- Requires-Dist: opentelemetry-instrumentation-mcp>=0.40.3; python_version >= '3.10' and python_version < '4.0'
225
- Requires-Dist: opentelemetry-instrumentation-openai>=0.39.3; python_version >= '3.10' and python_version < '4.0'
225
+ Requires-Dist: opentelemetry-instrumentation-mcp>=0.40.7; python_version >= '3.10' and python_version < '4.0'
226
+ Requires-Dist: opentelemetry-instrumentation-openai>=0.0.40.7; python_version >= '3.10' and python_version < '4.0'
226
227
  Requires-Dist: prompt-toolkit>=3.0.50
227
228
  Requires-Dist: pydantic-settings>=2.7.0
228
229
  Requires-Dist: pydantic>=2.10.4
@@ -286,11 +287,13 @@ Start by installing the [uv package manager](https://docs.astral.sh/uv/) for Pyt
286
287
 
287
288
  ```bash
288
289
  uv pip install fast-agent-mcp # install fast-agent!
289
-
290
- uv run fast-agent setup # create an example agent and config files
290
+ fast-agent go # start an interactive session
291
+ fast-agent go https://hf.co/mcp # with a remote MCP
292
+ fast-agent go --model=generic.qwen2.5 # use ollama qwen 2.5
293
+ fast-agent setup # create an example agent and config files
291
294
  uv run agent.py # run your first agent
292
295
  uv run agent.py --model=o3-mini.low # specify a model
293
- uv run fast-agent quickstart workflow # create "building effective agents" examples
296
+ fast-agent quickstart workflow # create "building effective agents" examples
294
297
  ```
295
298
 
296
299
  Other quickstart examples include a Researcher Agent (with Evaluator-Optimizer workflow) and Data Analysis Agent (similar to the ChatGPT experience), demonstrating MCP Roots support.
@@ -1,11 +1,11 @@
1
1
  mcp_agent/__init__.py,sha256=18T0AG0W9sJhTY38O9GFFOzliDhxx9p87CvRyti9zbw,1620
2
- mcp_agent/app.py,sha256=WRsiUdwy_9IAnaGRDwuLm7pzgQpt2wgsg10vBOpfcwM,5539
2
+ mcp_agent/app.py,sha256=3mtHP1nRQcRaKhhxgTmCOv00alh70nT7UxNA8bN47QE,5560
3
3
  mcp_agent/config.py,sha256=ITwLZ-Wzn8I2xYOMDP9XvNwZTLzzUbvQNnnna7PxflQ,13438
4
4
  mcp_agent/console.py,sha256=Gjf2QLFumwG1Lav__c07X_kZxxEUSkzV-1_-YbAwcwo,813
5
- mcp_agent/context.py,sha256=W9a91UVrpaT-GgoCtJp_ccHXnhmr8HaATDw7srm9NpU,7559
5
+ mcp_agent/context.py,sha256=H7JbaZ_8SzzTagLmIgUPUPxX5370C5qjQAsasFPZG2Y,7510
6
6
  mcp_agent/context_dependent.py,sha256=QXfhw3RaQCKfscEEBRGuZ3sdMWqkgShz2jJ1ivGGX1I,1455
7
7
  mcp_agent/event_progress.py,sha256=040lrCCclcOuryi07YGSej25kTQF5_JMXY12Yj-3u1U,2773
8
- mcp_agent/mcp_server_registry.py,sha256=cZJJEXvN_FEUlTFWPtr2q2mRcDjPGBWVmRifc1TnLa0,12042
8
+ mcp_agent/mcp_server_registry.py,sha256=b3iSb-0ULYc5yUG2KHav41WGwSYWiJCGQsOwWHWByxo,12346
9
9
  mcp_agent/progress_display.py,sha256=GeJU9VUt6qKsFVymG688hCMVCsAygG9ifiiEb5IcbN4,361
10
10
  mcp_agent/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  mcp_agent/agents/agent.py,sha256=EAYlcP1qqI1D0_CS808I806z1048FBjZQxxpcCZPeIU,3154
@@ -26,7 +26,7 @@ mcp_agent/cli/commands/check_config.py,sha256=KJbXUFx5Qih3lb_r-Fcx_uAjgHhgD7qqPe
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
29
- mcp_agent/cli/commands/url_parser.py,sha256=7QL9bp9tO7w0cPnwhbpt8GwjbOJ1Rrry1o71uVJhSss,5655
29
+ mcp_agent/cli/commands/url_parser.py,sha256=5VdtcHRHzi67YignStVbz7u-rcvNNErw9oJLAUFOtEY,5855
30
30
  mcp_agent/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
31
  mcp_agent/core/agent_app.py,sha256=aVvOzMrXZ3TfRGyAsnvcrMMYZxBf8Saa0UuHiA7DV0w,9922
32
32
  mcp_agent/core/agent_types.py,sha256=bQVQMTwKH7qHIJsNglj4C_d6PNFBBzC_0RIkcENSII4,1459
@@ -36,7 +36,7 @@ mcp_agent/core/enhanced_prompt.py,sha256=bzvcengS7XzHWB7NWhyxHM3hhO2HI4zP5DbGXAO
36
36
  mcp_agent/core/error_handling.py,sha256=xoyS2kLe0eG0bj2eSJCJ2odIhGUve2SbDR7jP-A-uRw,624
37
37
  mcp_agent/core/exceptions.py,sha256=ENAD_qGG67foxy6vDkIvc-lgopIUQy6O7zvNPpPXaQg,2289
38
38
  mcp_agent/core/fastagent.py,sha256=uS_NSXeniUYFu6xce8OHGJ9PbEYNU-gm1XVpa1r0rZc,22893
39
- mcp_agent/core/interactive_prompt.py,sha256=QuPdJQTBwXnnKyp52QjeVGyaXMiMm3fZK6g1ly2X7v4,24696
39
+ mcp_agent/core/interactive_prompt.py,sha256=JKEU1Gvq6zXaLniDPx8wll08ZTC6g1rQflL7khmnhs8,24710
40
40
  mcp_agent/core/mcp_content.py,sha256=2D7KHY9mG_vxoDwFLKvsPQV9VRIzHItM7V-jcEnACh8,8878
41
41
  mcp_agent/core/prompt.py,sha256=qnintOUGEoDPYLI9bu9G2OlgVMCe5ZPUZilgMzydXhc,7919
42
42
  mcp_agent/core/request_params.py,sha256=qmFWZXeYEJyYw2IwonyrTnZWxQG7qX6bKpOPcqETa60,1603
@@ -64,14 +64,14 @@ mcp_agent/llm/providers/__init__.py,sha256=heVxtmuqFJOnjjxHz4bWSqTAxXoN1E8twC_gQ
64
64
  mcp_agent/llm/providers/anthropic_utils.py,sha256=vYDN5G5jKMhD2CQg8veJYab7tvvzYkDMq8M1g_hUAQg,3275
65
65
  mcp_agent/llm/providers/augmented_llm_anthropic.py,sha256=gK_IvllVBNJUUrSfpgFpdhM-d4liCt0MLq7d2lXS7RI,15510
66
66
  mcp_agent/llm/providers/augmented_llm_azure.py,sha256=VPrD6lNrEw6EdYUTa9MDvHDNIPjJU5CG5xnKCM3JYdA,5878
67
- mcp_agent/llm/providers/augmented_llm_deepseek.py,sha256=NiZK5nv91ZS2VgVFXpbsFNFYLsLcppcbo_RstlRMd7I,1145
67
+ mcp_agent/llm/providers/augmented_llm_deepseek.py,sha256=GFLzITAsUPUXpQ_SKFfAvxYb1kCk1tlcjMnkHnEHNxY,3228
68
68
  mcp_agent/llm/providers/augmented_llm_generic.py,sha256=5Uq8ZBhcFuQTt7koP_5ykolREh2iWu8zKhNbh3pM9lQ,1210
69
69
  mcp_agent/llm/providers/augmented_llm_google_native.py,sha256=Axk6oKH5ctB6rXGnCjRKVkJq6O7rRqlD7aJ2He6UuZ8,20406
70
70
  mcp_agent/llm/providers/augmented_llm_google_oai.py,sha256=cO4dvjTl9ymqEurCOo5nP09ATfXVjgkuk1yZAlWpS1s,1137
71
71
  mcp_agent/llm/providers/augmented_llm_openai.py,sha256=5CFHKayjm-aeCBpohIK3WelAEuX7_LDGZIKnWR_rq-s,14577
72
72
  mcp_agent/llm/providers/augmented_llm_openrouter.py,sha256=V_TlVKm92GHBxYIo6gpvH_6cAaIdppS25Tz6x5T7LW0,2341
73
73
  mcp_agent/llm/providers/augmented_llm_tensorzero.py,sha256=Mol_Wzj_ZtccW-LMw0oFwWUt1m1yfofloay9QYNP23c,20729
74
- mcp_agent/llm/providers/google_converter.py,sha256=bA0oYdB6tfRX_iuwTr8xTBYWlzNNskwYIS3Y9aFyEbo,16643
74
+ mcp_agent/llm/providers/google_converter.py,sha256=zsqxJJ636WzCL2K6w-yB94O8bdNR6mo8f5mQEnUJFyg,16831
75
75
  mcp_agent/llm/providers/multipart_converter_anthropic.py,sha256=t5lHYGfFUacJldnrVtMNW-8gEMoto8Y7hJkDrnyZR-Y,16650
76
76
  mcp_agent/llm/providers/multipart_converter_openai.py,sha256=XPIulWntNpZWNGWrc240StPzok2RqrDAV7OigDwQ1uU,15850
77
77
  mcp_agent/llm/providers/multipart_converter_tensorzero.py,sha256=BFTdyVk42HZskDAuTHicfDTUJq89d1fz8C9nAOuHxlE,8646
@@ -87,11 +87,12 @@ mcp_agent/logging/logger.py,sha256=l02OGX_c5FOyH0rspd4ZvnkJcbb0FahhUhlh2KI8mqE,1
87
87
  mcp_agent/logging/rich_progress.py,sha256=oY9fjb4Tyw6887v8sgO6EGIK4lnmIoR3NNxhA_-Ln_M,4893
88
88
  mcp_agent/logging/transport.py,sha256=m8YsLLu5T8eof_ndpLQs4gHOzqqEL98xsVwBwDsBfxI,17335
89
89
  mcp_agent/mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
- mcp_agent/mcp/common.py,sha256=DiWLH9rxWvCgkKRsHQehY9mDhQl9gki1-q7LVUflDvI,425
90
+ mcp_agent/mcp/common.py,sha256=MpSC0fLO21RcDz4VApah4C8_LisVGz7OXkR17Xw-9mY,431
91
91
  mcp_agent/mcp/gen_client.py,sha256=fAVwFVCgSamw4PwoWOV4wrK9TABx1S_zZv8BctRyF2k,3030
92
+ mcp_agent/mcp/hf_auth.py,sha256=-ZefZ7Vh_hQngVYKUT1BDya84gxbwpRiWqxaHEb3k5E,2430
92
93
  mcp_agent/mcp/interfaces.py,sha256=PAou8znAl2HgtvfCpLQOZFbKra9F72OcVRfBJbboNX8,6965
93
94
  mcp_agent/mcp/logger_textio.py,sha256=vljC1BtNTCxBAda9ExqNB-FwVNUZIuJT3h1nWmCjMws,3172
94
- mcp_agent/mcp/mcp_agent_client_session.py,sha256=1zlBZaGRvCHuflrTd4dUeC4BzcPXxfPuciulJB0DIFI,7900
95
+ mcp_agent/mcp/mcp_agent_client_session.py,sha256=V17Lj21rMGIKKVAIyNx5l5gmC8jQuohjJGpRcoCXfVA,6862
95
96
  mcp_agent/mcp/mcp_aggregator.py,sha256=Mdmr-6gNlrcofHzhHZloz1QVbC5ZAnCSPNFY5fwm-Bs,47075
96
97
  mcp_agent/mcp/mcp_connection_manager.py,sha256=5JekxOJsB46spHsiXt7pyRPicg8TGHMiSJRtXRW2JB8,17074
97
98
  mcp_agent/mcp/mime_utils.py,sha256=difepNR_gpb4MpMLkBRAoyhDk-AjXUHTiqKvT_VwS1o,1805
@@ -153,8 +154,8 @@ mcp_agent/resources/examples/workflows/router.py,sha256=E4x_-c3l4YW9w1i4ARcDtkde
153
154
  mcp_agent/resources/examples/workflows/short_story.txt,sha256=X3y_1AyhLFN2AKzCKvucJtDgAFIJfnlbsbGZO5bBWu0,1187
154
155
  mcp_agent/tools/tool_definition.py,sha256=L3Pxl-uLEXqlVoo-bYuFTFALeI-2pIU44YgFhsTKEtM,398
155
156
  mcp_agent/ui/console_display.py,sha256=UKqax5V2TC0hkZZORmmd6UqUk0DGX7A25E3h1k9f42k,10982
156
- fast_agent_mcp-0.2.28.dist-info/METADATA,sha256=DbIbErJ9uZh6NnL6Tl-OD4ihPgzSRXR_ExDpafUwGKQ,30581
157
- fast_agent_mcp-0.2.28.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
158
- fast_agent_mcp-0.2.28.dist-info/entry_points.txt,sha256=bRniFM5zk3Kix5z7scX0gf9VnmGQ2Cz_Q1Gh7Ir4W00,186
159
- fast_agent_mcp-0.2.28.dist-info/licenses/LICENSE,sha256=cN3FxDURL9XuzE5mhK9L2paZo82LTfjwCYVT7e3j0e4,10939
160
- fast_agent_mcp-0.2.28.dist-info/RECORD,,
157
+ fast_agent_mcp-0.2.29.dist-info/METADATA,sha256=diqb8oCcIBC11MDR4NLqMVQVLy_jmCP7x5lfvuviRYE,30799
158
+ fast_agent_mcp-0.2.29.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
159
+ fast_agent_mcp-0.2.29.dist-info/entry_points.txt,sha256=bRniFM5zk3Kix5z7scX0gf9VnmGQ2Cz_Q1Gh7Ir4W00,186
160
+ fast_agent_mcp-0.2.29.dist-info/licenses/LICENSE,sha256=cN3FxDURL9XuzE5mhK9L2paZo82LTfjwCYVT7e3j0e4,10939
161
+ fast_agent_mcp-0.2.29.dist-info/RECORD,,
mcp_agent/app.py CHANGED
@@ -119,7 +119,7 @@ class MCPApp:
119
119
  if self._initialized:
120
120
  return
121
121
 
122
- self._context = await initialize_context(self._config_or_path)
122
+ self._context = await initialize_context(self._config_or_path, store_globally=True)
123
123
 
124
124
  # Set the properties that were passed in the constructor
125
125
  self._context.human_input_handler = self._human_input_callback
@@ -8,6 +8,8 @@ import re
8
8
  from typing import Dict, List, Literal, Tuple
9
9
  from urllib.parse import urlparse
10
10
 
11
+ from mcp_agent.mcp.hf_auth import add_hf_auth_header
12
+
11
13
 
12
14
  def parse_server_url(
13
15
  url: str,
@@ -131,7 +133,11 @@ def parse_server_urls(
131
133
  result = []
132
134
  for url in url_list:
133
135
  server_name, transport_type, parsed_url = parse_server_url(url)
134
- result.append((server_name, transport_type, parsed_url, headers))
136
+
137
+ # Apply HuggingFace authentication if appropriate
138
+ final_headers = add_hf_auth_header(parsed_url, headers)
139
+
140
+ result.append((server_name, transport_type, parsed_url, final_headers))
135
141
 
136
142
  return result
137
143
 
mcp_agent/context.py CHANGED
@@ -12,7 +12,8 @@ from opentelemetry import trace
12
12
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
13
13
  from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
14
14
  from opentelemetry.instrumentation.google_genai import GoogleGenAiSdkInstrumentor
15
- from opentelemetry.instrumentation.mcp import McpInstrumentor
15
+
16
+ # from opentelemetry.instrumentation.mcp import McpInstrumentor
16
17
  from opentelemetry.instrumentation.openai import OpenAIInstrumentor
17
18
  from opentelemetry.propagate import set_global_textmap
18
19
  from opentelemetry.sdk.resources import Resource
@@ -114,7 +115,9 @@ async def configure_otel(config: "Settings") -> None:
114
115
  AnthropicInstrumentor().instrument()
115
116
  OpenAIInstrumentor().instrument()
116
117
  GoogleGenAiSdkInstrumentor().instrument()
117
- McpInstrumentor().instrument()
118
+
119
+
120
+ # McpInstrumentor().instrument()
118
121
 
119
122
 
120
123
  async def configure_logger(config: "Settings") -> None:
@@ -198,7 +201,6 @@ _global_context: Context | None = None
198
201
  def get_current_context() -> Context:
199
202
  """
200
203
  Synchronous initializer/getter for global application context.
201
- For async usage, use aget_current_context instead.
202
204
  """
203
205
  global _global_context
204
206
  if _global_context is None:
@@ -351,7 +351,7 @@ class InteractivePrompt:
351
351
  for prompt in prompts:
352
352
  # Get basic prompt info
353
353
  prompt_name = getattr(prompt, "name", "Unknown")
354
- description = getattr(prompt, "description", "No description")
354
+ prompt_description = getattr(prompt, "description", "No description")
355
355
 
356
356
  # Extract argument information
357
357
  arg_names = []
@@ -387,7 +387,7 @@ class InteractivePrompt:
387
387
  "server": server_name,
388
388
  "name": prompt_name,
389
389
  "namespaced_name": namespaced_name,
390
- "description": description,
390
+ "description": prompt_description,
391
391
  "arg_count": len(arg_names),
392
392
  "arg_names": arg_names,
393
393
  "required_args": required_args,
@@ -1,6 +1,10 @@
1
+ from typing import List, Tuple, Type
2
+
1
3
  from mcp_agent.core.request_params import RequestParams
2
4
  from mcp_agent.llm.provider_types import Provider
3
5
  from mcp_agent.llm.providers.augmented_llm_openai import OpenAIAugmentedLLM
6
+ from mcp_agent.mcp.interfaces import ModelT
7
+ from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
4
8
 
5
9
  DEEPSEEK_BASE_URL = "https://api.deepseek.com"
6
10
  DEFAULT_DEEPSEEK_MODEL = "deepseekchat" # current Deepseek only has two type models
@@ -28,3 +32,48 @@ class DeepSeekAugmentedLLM(OpenAIAugmentedLLM):
28
32
  base_url = self.context.config.deepseek.base_url
29
33
 
30
34
  return base_url if base_url else DEEPSEEK_BASE_URL
35
+
36
+ async def _apply_prompt_provider_specific_structured(
37
+ self,
38
+ multipart_messages: List[PromptMessageMultipart],
39
+ model: Type[ModelT],
40
+ request_params: RequestParams | None = None,
41
+ ) -> Tuple[ModelT | None, PromptMessageMultipart]: # noqa: F821
42
+ request_params = self.get_request_params(request_params)
43
+
44
+ request_params.response_format = {"type": "json_object"}
45
+
46
+ # Get the full schema and extract just the properties
47
+ full_schema = model.model_json_schema()
48
+ properties = full_schema.get("properties", {})
49
+ required_fields = full_schema.get("required", [])
50
+
51
+ # Create a cleaner format description
52
+ format_description = "{\n"
53
+ for field_name, field_info in properties.items():
54
+ field_type = field_info.get("type", "string")
55
+ description = field_info.get("description", "")
56
+ format_description += f' "{field_name}": "{field_type}"'
57
+ if description:
58
+ format_description += f" // {description}"
59
+ if field_name in required_fields:
60
+ format_description += " // REQUIRED"
61
+ format_description += "\n"
62
+ format_description += "}"
63
+
64
+ multipart_messages[-1].add_text(
65
+ f"""YOU MUST RESPOND WITH A JSON OBJECT IN EXACTLY THIS FORMAT:
66
+ {format_description}
67
+
68
+ IMPORTANT RULES:
69
+ - Respond ONLY with the JSON object, no other text
70
+ - Do NOT include "properties" or "schema" wrappers
71
+ - Do NOT use code fences or markdown
72
+ - The response must be valid JSON that matches the format above
73
+ - All required fields must be included"""
74
+ )
75
+
76
+ result: PromptMessageMultipart = await self._apply_prompt_provider_specific(
77
+ multipart_messages, request_params
78
+ )
79
+ return self._structured_from_multipart(result, model)
@@ -166,6 +166,10 @@ class GoogleConverter:
166
166
  fast_agent_parts: List[
167
167
  TextContent | ImageContent | EmbeddedResource | CallToolRequestParams
168
168
  ] = []
169
+
170
+ if content is None or not hasattr(content, 'parts') or content.parts is None:
171
+ return [] # Google API response 'content' object is None. Cannot extract parts.
172
+
169
173
  for part in content.parts:
170
174
  if part.text:
171
175
  fast_agent_parts.append(TextContent(type="text", text=part.text))
mcp_agent/mcp/common.py CHANGED
@@ -8,9 +8,9 @@ SEP = "-"
8
8
 
9
9
  def create_namespaced_name(server_name: str, resource_name: str) -> str:
10
10
  """Create a namespaced resource name from server and resource names"""
11
- return f"{server_name}{SEP}{resource_name}"
11
+ return f"{server_name}{SEP}{resource_name}"[:64]
12
12
 
13
13
 
14
14
  def is_namespaced_name(name: str) -> bool:
15
15
  """Check if a name is already namespaced"""
16
- return SEP in name
16
+ return SEP in name
@@ -0,0 +1,87 @@
1
+ """HuggingFace authentication utilities for MCP connections."""
2
+
3
+ import os
4
+ from typing import Dict, Optional
5
+ from urllib.parse import urlparse
6
+
7
+
8
+ def is_huggingface_url(url: str) -> bool:
9
+ """
10
+ Check if a URL is a HuggingFace URL that should receive HF_TOKEN authentication.
11
+
12
+ Args:
13
+ url: The URL to check
14
+
15
+ Returns:
16
+ True if the URL is a HuggingFace URL, False otherwise
17
+ """
18
+ try:
19
+ parsed = urlparse(url)
20
+ hostname = parsed.hostname
21
+ if hostname is None:
22
+ return False
23
+
24
+ # Check for HuggingFace domains
25
+ return hostname in {"hf.co", "huggingface.co"}
26
+ except Exception:
27
+ return False
28
+
29
+
30
+ def get_hf_token_from_env() -> Optional[str]:
31
+ """
32
+ Get the HuggingFace token from the HF_TOKEN environment variable.
33
+
34
+ Returns:
35
+ The HF_TOKEN value if set, None otherwise
36
+ """
37
+ return os.environ.get("HF_TOKEN")
38
+
39
+
40
+ def should_add_hf_auth(url: str, existing_headers: Optional[Dict[str, str]]) -> bool:
41
+ """
42
+ Determine if HuggingFace authentication should be added to the headers.
43
+
44
+ Args:
45
+ url: The URL to check
46
+ existing_headers: Existing headers dictionary (may be None)
47
+
48
+ Returns:
49
+ True if HF auth should be added, False otherwise
50
+ """
51
+ # Only add HF auth if:
52
+ # 1. URL is a HuggingFace URL
53
+ # 2. No existing Authorization header is set
54
+ # 3. HF_TOKEN environment variable is available
55
+
56
+ if not is_huggingface_url(url):
57
+ return False
58
+
59
+ if existing_headers and "Authorization" in existing_headers:
60
+ return False
61
+
62
+ return get_hf_token_from_env() is not None
63
+
64
+
65
+ def add_hf_auth_header(url: str, headers: Optional[Dict[str, str]]) -> Optional[Dict[str, str]]:
66
+ """
67
+ Add HuggingFace authentication header if appropriate.
68
+
69
+ Args:
70
+ url: The URL to check
71
+ headers: Existing headers dictionary (may be None)
72
+
73
+ Returns:
74
+ Updated headers dictionary with HF auth if appropriate, or original headers
75
+ """
76
+ if not should_add_hf_auth(url, headers):
77
+ return headers
78
+
79
+ hf_token = get_hf_token_from_env()
80
+ if hf_token is None:
81
+ return headers
82
+
83
+ # Create new headers dict or copy existing one
84
+ result_headers = dict(headers) if headers else {}
85
+ result_headers["Authorization"] = f"Bearer {hf_token}"
86
+
87
+ return result_headers
@@ -7,15 +7,13 @@ from datetime import timedelta
7
7
  from typing import TYPE_CHECKING
8
8
 
9
9
  from mcp import ClientSession, ServerNotification
10
+ from mcp.shared.message import MessageMetadata
10
11
  from mcp.shared.session import (
11
12
  ProgressFnT,
12
13
  ReceiveResultT,
13
- RequestId,
14
- SendNotificationT,
15
14
  SendRequestT,
16
- SendResultT,
17
15
  )
18
- from mcp.types import ErrorData, Implementation, ListRootsResult, Root, ToolListChangedNotification
16
+ from mcp.types import Implementation, ListRootsResult, Root, ToolListChangedNotification
19
17
  from pydantic import FileUrl
20
18
 
21
19
  from mcp_agent.context_dependent import ContextDependent
@@ -76,12 +74,16 @@ class MCPAgentClientSession(ClientSession, ContextDependent):
76
74
 
77
75
  # Only register callbacks if the server_config has the relevant settings
78
76
  list_roots_cb = list_roots if (self.server_config and self.server_config.roots) else None
79
-
77
+
80
78
  # Register sampling callback if either:
81
79
  # 1. Sampling is explicitly configured, OR
82
80
  # 2. Application-level auto_sampling is enabled
83
81
  sampling_cb = None
84
- if self.server_config and hasattr(self.server_config, "sampling") and self.server_config.sampling:
82
+ if (
83
+ self.server_config
84
+ and hasattr(self.server_config, "sampling")
85
+ and self.server_config.sampling
86
+ ):
85
87
  # Explicit sampling configuration
86
88
  sampling_cb = sample
87
89
  elif self._should_enable_auto_sampling():
@@ -100,9 +102,10 @@ class MCPAgentClientSession(ClientSession, ContextDependent):
100
102
  """Check if auto_sampling is enabled at the application level."""
101
103
  try:
102
104
  from mcp_agent.context import get_current_context
105
+
103
106
  context = get_current_context()
104
107
  if context and context.config:
105
- return getattr(context.config, 'auto_sampling', True)
108
+ return getattr(context.config, "auto_sampling", True)
106
109
  except Exception:
107
110
  pass
108
111
  return True # Default to True if can't access config
@@ -112,6 +115,7 @@ class MCPAgentClientSession(ClientSession, ContextDependent):
112
115
  request: SendRequestT,
113
116
  result_type: type[ReceiveResultT],
114
117
  request_read_timeout_seconds: timedelta | None = None,
118
+ metadata: MessageMetadata | None = None,
115
119
  progress_callback: ProgressFnT | None = None,
116
120
  ) -> ReceiveResultT:
117
121
  logger.debug("send_request: request=", data=request.model_dump())
@@ -120,32 +124,18 @@ class MCPAgentClientSession(ClientSession, ContextDependent):
120
124
  request=request,
121
125
  result_type=result_type,
122
126
  request_read_timeout_seconds=request_read_timeout_seconds,
127
+ metadata=metadata,
123
128
  progress_callback=progress_callback,
124
- metadata=None,
125
129
  )
126
- logger.debug("send_request: response=", data=result.model_dump())
130
+ logger.debug(
131
+ "send_request: response=",
132
+ data=result.model_dump() if result is not None else "no response returned",
133
+ )
127
134
  return result
128
135
  except Exception as e:
129
136
  logger.error(f"send_request failed: {str(e)}")
130
137
  raise
131
138
 
132
- async def send_notification(self, notification: SendNotificationT) -> None:
133
- logger.debug("send_notification:", data=notification.model_dump())
134
- try:
135
- return await super().send_notification(notification)
136
- except Exception as e:
137
- logger.error("send_notification failed", data=e)
138
- raise
139
-
140
- async def _send_response(
141
- self, request_id: RequestId, response: SendResultT | ErrorData
142
- ) -> None:
143
- logger.debug(
144
- f"send_response: request_id={request_id}, response=",
145
- data=response.model_dump(),
146
- )
147
- return await super()._send_response(request_id, response)
148
-
149
139
  async def _received_notification(self, notification: ServerNotification) -> None:
150
140
  """
151
141
  Can be overridden by subclasses to handle a notification without needing
@@ -189,17 +179,3 @@ class MCPAgentClientSession(ClientSession, ContextDependent):
189
179
  await self._tool_list_changed_callback(server_name)
190
180
  except Exception as e:
191
181
  logger.error(f"Error in tool list changed callback: {e}")
192
-
193
- async def send_progress_notification(
194
- self, progress_token: str | int, progress: float, total: float | None = None
195
- ) -> None:
196
- """
197
- Sends a progress notification for a request that is currently being
198
- processed.
199
- """
200
- logger.debug(
201
- "send_progress_notification: progress_token={progress_token}, progress={progress}, total={total}"
202
- )
203
- return await super().send_progress_notification(
204
- progress_token=progress_token, progress=progress, total=total
205
- )
@@ -27,6 +27,7 @@ from mcp_agent.config import (
27
27
  get_settings,
28
28
  )
29
29
  from mcp_agent.logging.logger import get_logger
30
+ from mcp_agent.mcp.hf_auth import add_hf_auth_header
30
31
  from mcp_agent.mcp.logger_textio import get_stderr_handler
31
32
  from mcp_agent.mcp.mcp_connection_manager import (
32
33
  MCPConnectionManager,
@@ -176,11 +177,14 @@ class ServerRegistry:
176
177
  if not config.url:
177
178
  raise ValueError(f"URL is required for SSE transport: {server_name}")
178
179
 
180
+ # Apply HuggingFace authentication if appropriate
181
+ headers = add_hf_auth_header(config.url, config.headers)
182
+
179
183
  # Use sse_client to get the read and write streams
180
184
  async with _add_none_to_context(
181
185
  sse_client(
182
186
  config.url,
183
- config.headers,
187
+ headers,
184
188
  sse_read_timeout=config.read_transport_sse_timeout_seconds,
185
189
  )
186
190
  ) as (read_stream, write_stream, _):
@@ -198,9 +202,12 @@ class ServerRegistry:
198
202
  logger.debug(f"{server_name}: Closed session to server")
199
203
  elif config.transport == "http":
200
204
  if not config.url:
201
- raise ValueError(f"URL is required for SSE transport: {server_name}")
205
+ raise ValueError(f"URL is required for HTTP transport: {server_name}")
206
+
207
+ # Apply HuggingFace authentication if appropriate
208
+ headers = add_hf_auth_header(config.url, config.headers)
202
209
 
203
- async with streamablehttp_client(config.url, config.headers) as (
210
+ async with streamablehttp_client(config.url, headers) as (
204
211
  read_stream,
205
212
  write_stream,
206
213
  _,