fast-agent-mcp 0.2.20__py3-none-any.whl → 0.2.22__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.
Potentially problematic release.
This version of fast-agent-mcp might be problematic. Click here for more details.
- {fast_agent_mcp-0.2.20.dist-info → fast_agent_mcp-0.2.22.dist-info}/METADATA +9 -7
- {fast_agent_mcp-0.2.20.dist-info → fast_agent_mcp-0.2.22.dist-info}/RECORD +20 -18
- mcp_agent/cli/commands/go.py +49 -11
- mcp_agent/config.py +13 -0
- mcp_agent/core/request_params.py +6 -1
- mcp_agent/event_progress.py +1 -1
- mcp_agent/llm/augmented_llm.py +3 -9
- mcp_agent/llm/model_factory.py +8 -0
- mcp_agent/llm/provider_types.py +1 -0
- mcp_agent/llm/providers/augmented_llm_anthropic.py +1 -0
- mcp_agent/llm/providers/augmented_llm_openai.py +14 -1
- mcp_agent/llm/providers/augmented_llm_tensorzero.py +442 -0
- mcp_agent/llm/providers/multipart_converter_tensorzero.py +200 -0
- mcp_agent/mcp/mcp_agent_client_session.py +5 -1
- mcp_agent/mcp/mcp_aggregator.py +22 -17
- mcp_agent/mcp/mcp_connection_manager.py +46 -4
- mcp_agent/mcp/prompts/prompt_server.py +12 -1
- {fast_agent_mcp-0.2.20.dist-info → fast_agent_mcp-0.2.22.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.20.dist-info → fast_agent_mcp-0.2.22.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.2.20.dist-info → fast_agent_mcp-0.2.22.dist-info}/licenses/LICENSE +0 -0
mcp_agent/mcp/mcp_aggregator.py
CHANGED
@@ -41,10 +41,12 @@ SEP = "-"
|
|
41
41
|
T = TypeVar("T")
|
42
42
|
R = TypeVar("R")
|
43
43
|
|
44
|
+
|
44
45
|
def create_namespaced_name(server_name: str, resource_name: str) -> str:
|
45
46
|
"""Create a namespaced resource name from server and resource names"""
|
46
47
|
return f"{server_name}{SEP}{resource_name}"
|
47
48
|
|
49
|
+
|
48
50
|
def is_namespaced_name(name: str) -> bool:
|
49
51
|
"""Check if a name is already namespaced"""
|
50
52
|
return SEP in name
|
@@ -325,14 +327,14 @@ class MCPAggregator(ContextDependent):
|
|
325
327
|
except Exception as e:
|
326
328
|
logger.debug(f"Error getting capabilities for server '{server_name}': {e}")
|
327
329
|
return None
|
328
|
-
|
330
|
+
|
329
331
|
async def validate_server(self, server_name: str) -> bool:
|
330
332
|
"""
|
331
333
|
Validate that a server exists in our server list.
|
332
|
-
|
334
|
+
|
333
335
|
Args:
|
334
336
|
server_name: Name of the server to validate
|
335
|
-
|
337
|
+
|
336
338
|
Returns:
|
337
339
|
True if the server exists, False otherwise
|
338
340
|
"""
|
@@ -340,25 +342,25 @@ class MCPAggregator(ContextDependent):
|
|
340
342
|
if not valid:
|
341
343
|
logger.debug(f"Server '{server_name}' not found")
|
342
344
|
return valid
|
343
|
-
|
345
|
+
|
344
346
|
async def server_supports_feature(self, server_name: str, feature: str) -> bool:
|
345
347
|
"""
|
346
348
|
Check if a server supports a specific feature.
|
347
|
-
|
349
|
+
|
348
350
|
Args:
|
349
351
|
server_name: Name of the server to check
|
350
352
|
feature: Feature to check for (e.g., "prompts", "resources")
|
351
|
-
|
353
|
+
|
352
354
|
Returns:
|
353
355
|
True if the server supports the feature, False otherwise
|
354
356
|
"""
|
355
357
|
if not await self.validate_server(server_name):
|
356
358
|
return False
|
357
|
-
|
359
|
+
|
358
360
|
capabilities = await self.get_capabilities(server_name)
|
359
361
|
if not capabilities:
|
360
362
|
return False
|
361
|
-
|
363
|
+
|
362
364
|
return getattr(capabilities, feature, False)
|
363
365
|
|
364
366
|
async def list_servers(self) -> List[str]:
|
@@ -465,26 +467,26 @@ class MCPAggregator(ContextDependent):
|
|
465
467
|
if resource_type == "tool" and name in self._namespaced_tool_map:
|
466
468
|
namespaced_tool = self._namespaced_tool_map[name]
|
467
469
|
return namespaced_tool.server_name, namespaced_tool.tool.name
|
468
|
-
|
470
|
+
|
469
471
|
# Next, attempt to interpret as a namespaced name
|
470
472
|
if is_namespaced_name(name):
|
471
473
|
parts = name.split(SEP, 1)
|
472
474
|
server_name, local_name = parts[0], parts[1]
|
473
|
-
|
475
|
+
|
474
476
|
# Validate that the parsed server actually exists
|
475
477
|
if server_name in self.server_names:
|
476
478
|
return server_name, local_name
|
477
|
-
|
479
|
+
|
478
480
|
# If the server name doesn't exist, it might be a tool with a hyphen in its name
|
479
481
|
# Fall through to the next checks
|
480
|
-
|
482
|
+
|
481
483
|
# For tools, search all servers for the tool by exact name match
|
482
484
|
if resource_type == "tool":
|
483
485
|
for server_name, tools in self._server_to_tool_map.items():
|
484
486
|
for namespaced_tool in tools:
|
485
487
|
if namespaced_tool.tool.name == name:
|
486
488
|
return server_name, name
|
487
|
-
|
489
|
+
|
488
490
|
# For all other resource types, use the first server
|
489
491
|
return (self.server_names[0] if self.server_names else None, name)
|
490
492
|
|
@@ -524,7 +526,10 @@ class MCPAggregator(ContextDependent):
|
|
524
526
|
operation_type="tool",
|
525
527
|
operation_name=local_tool_name,
|
526
528
|
method_name="call_tool",
|
527
|
-
method_args={
|
529
|
+
method_args={
|
530
|
+
"name": local_tool_name,
|
531
|
+
"arguments": arguments,
|
532
|
+
},
|
528
533
|
error_factory=lambda msg: CallToolResult(
|
529
534
|
isError=True, content=[TextContent(type="text", text=msg)]
|
530
535
|
),
|
@@ -558,7 +563,7 @@ class MCPAggregator(ContextDependent):
|
|
558
563
|
elif is_namespaced_name(prompt_name):
|
559
564
|
parts = prompt_name.split(SEP, 1)
|
560
565
|
potential_server = parts[0]
|
561
|
-
|
566
|
+
|
562
567
|
# Only treat as namespaced if the server part is valid
|
563
568
|
if potential_server in self.server_names:
|
564
569
|
server_name = potential_server
|
@@ -570,7 +575,7 @@ class MCPAggregator(ContextDependent):
|
|
570
575
|
else:
|
571
576
|
local_prompt_name = prompt_name
|
572
577
|
# We'll search all servers below
|
573
|
-
|
578
|
+
|
574
579
|
# If we have a specific server to check
|
575
580
|
if server_name:
|
576
581
|
if not await self.validate_server(server_name):
|
@@ -579,7 +584,7 @@ class MCPAggregator(ContextDependent):
|
|
579
584
|
description=f"Error: Server '{server_name}' not found",
|
580
585
|
messages=[],
|
581
586
|
)
|
582
|
-
|
587
|
+
|
583
588
|
# Check if server supports prompts
|
584
589
|
if not await self.server_supports_feature(server_name, "prompts"):
|
585
590
|
logger.debug(f"Server '{server_name}' does not support prompts")
|
@@ -15,6 +15,7 @@ from typing import (
|
|
15
15
|
|
16
16
|
from anyio import Event, Lock, create_task_group
|
17
17
|
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
18
|
+
from httpx import HTTPStatusError
|
18
19
|
from mcp import ClientSession
|
19
20
|
from mcp.client.sse import sse_client
|
20
21
|
from mcp.client.stdio import (
|
@@ -162,14 +163,26 @@ async def _server_lifecycle_task(server_conn: ServerConnection) -> None:
|
|
162
163
|
transport_context = server_conn._transport_context_factory()
|
163
164
|
|
164
165
|
async with transport_context as (read_stream, write_stream):
|
165
|
-
# try:
|
166
166
|
server_conn.create_session(read_stream, write_stream)
|
167
167
|
|
168
168
|
async with server_conn.session:
|
169
169
|
await server_conn.initialize_session()
|
170
|
-
|
171
170
|
await server_conn.wait_for_shutdown_request()
|
172
171
|
|
172
|
+
except HTTPStatusError as http_exc:
|
173
|
+
logger.error(
|
174
|
+
f"{server_name}: Lifecycle task encountered HTTP error: {http_exc}",
|
175
|
+
exc_info=True,
|
176
|
+
data={
|
177
|
+
"progress_action": ProgressAction.FATAL_ERROR,
|
178
|
+
"server_name": server_name,
|
179
|
+
},
|
180
|
+
)
|
181
|
+
server_conn._error_occurred = True
|
182
|
+
server_conn._error_message = f"HTTP Error: {http_exc.response.status_code} {http_exc.response.reason_phrase} for URL: {http_exc.request.url}"
|
183
|
+
server_conn._initialized_event.set()
|
184
|
+
# No raise - let get_server handle it with a friendly message
|
185
|
+
|
173
186
|
except Exception as exc:
|
174
187
|
logger.error(
|
175
188
|
f"{server_name}: Lifecycle task encountered an error: {exc}",
|
@@ -180,7 +193,27 @@ async def _server_lifecycle_task(server_conn: ServerConnection) -> None:
|
|
180
193
|
},
|
181
194
|
)
|
182
195
|
server_conn._error_occurred = True
|
183
|
-
|
196
|
+
|
197
|
+
if "ExceptionGroup" in type(exc).__name__ and hasattr(exc, "exceptions"):
|
198
|
+
# Handle ExceptionGroup better by extracting the actual errors
|
199
|
+
error_messages = []
|
200
|
+
for subexc in exc.exceptions:
|
201
|
+
if isinstance(subexc, HTTPStatusError):
|
202
|
+
# Special handling for HTTP errors to make them more user-friendly
|
203
|
+
error_messages.append(
|
204
|
+
f"HTTP Error: {subexc.response.status_code} {subexc.response.reason_phrase} for URL: {subexc.request.url}"
|
205
|
+
)
|
206
|
+
else:
|
207
|
+
error_messages.append(f"Error: {type(subexc).__name__}: {subexc}")
|
208
|
+
if hasattr(subexc, "__cause__") and subexc.__cause__:
|
209
|
+
error_messages.append(
|
210
|
+
f"Caused by: {type(subexc.__cause__).__name__}: {subexc.__cause__}"
|
211
|
+
)
|
212
|
+
server_conn._error_message = error_messages
|
213
|
+
else:
|
214
|
+
# For regular exceptions, keep the traceback but format it more cleanly
|
215
|
+
server_conn._error_message = traceback.format_exception(exc)
|
216
|
+
|
184
217
|
# If there's an error, we should also set the event so that
|
185
218
|
# 'get_server' won't hang
|
186
219
|
server_conn._initialized_event.set()
|
@@ -277,6 +310,7 @@ class MCPConnectionManager(ContextDependent):
|
|
277
310
|
config.headers,
|
278
311
|
sse_read_timeout=config.read_transport_sse_timeout_seconds,
|
279
312
|
)
|
313
|
+
|
280
314
|
else:
|
281
315
|
raise ValueError(f"Unsupported transport: {config.transport}")
|
282
316
|
|
@@ -333,9 +367,17 @@ class MCPConnectionManager(ContextDependent):
|
|
333
367
|
# Check if the server is healthy after initialization
|
334
368
|
if not server_conn.is_healthy():
|
335
369
|
error_msg = server_conn._error_message or "Unknown error"
|
370
|
+
|
371
|
+
# Format the error message for better display
|
372
|
+
if isinstance(error_msg, list):
|
373
|
+
# Join the list with newlines for better readability
|
374
|
+
formatted_error = "\n".join(error_msg)
|
375
|
+
else:
|
376
|
+
formatted_error = str(error_msg)
|
377
|
+
|
336
378
|
raise ServerInitializationError(
|
337
379
|
f"MCP Server: '{server_name}': Failed to initialize - see details. Check fastagent.config.yaml?",
|
338
|
-
|
380
|
+
formatted_error,
|
339
381
|
)
|
340
382
|
|
341
383
|
return server_conn
|
@@ -82,6 +82,7 @@ class PromptConfig(PromptMetadata):
|
|
82
82
|
http_timeout: float = 10.0
|
83
83
|
transport: str = "stdio"
|
84
84
|
port: int = 8000
|
85
|
+
host: str = "0.0.0.0"
|
85
86
|
|
86
87
|
|
87
88
|
# We'll maintain registries of all exposed resources and prompts
|
@@ -344,6 +345,12 @@ def parse_args():
|
|
344
345
|
default=8000,
|
345
346
|
help="Port to use for SSE transport (default: 8000)",
|
346
347
|
)
|
348
|
+
parser.add_argument(
|
349
|
+
"--host",
|
350
|
+
type=str,
|
351
|
+
default="0.0.0.0",
|
352
|
+
help="Host to bind to for SSE transport (default: 0.0.0.0)",
|
353
|
+
)
|
347
354
|
parser.add_argument(
|
348
355
|
"--test", type=str, help="Test a specific prompt without starting the server"
|
349
356
|
)
|
@@ -380,6 +387,7 @@ def initialize_config(args) -> PromptConfig:
|
|
380
387
|
http_timeout=args.http_timeout,
|
381
388
|
transport=args.transport,
|
382
389
|
port=args.port,
|
390
|
+
host=args.host,
|
383
391
|
)
|
384
392
|
|
385
393
|
|
@@ -497,7 +505,10 @@ async def async_main() -> int:
|
|
497
505
|
if config.transport == "stdio":
|
498
506
|
await mcp.run_stdio_async()
|
499
507
|
else: # sse
|
500
|
-
#
|
508
|
+
# Set the host and port in settings before running the server
|
509
|
+
mcp.settings.host = config.host
|
510
|
+
mcp.settings.port = config.port
|
511
|
+
logger.info(f"Starting SSE server on {config.host}:{config.port}")
|
501
512
|
await mcp.run_sse_async()
|
502
513
|
return 0
|
503
514
|
|
File without changes
|
File without changes
|
File without changes
|