fast-agent-mcp 0.1.11__py3-none-any.whl → 0.1.13__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.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/METADATA +1 -1
- fast_agent_mcp-0.1.13.dist-info/RECORD +164 -0
- mcp_agent/agents/agent.py +37 -102
- mcp_agent/app.py +16 -27
- mcp_agent/cli/commands/bootstrap.py +22 -52
- mcp_agent/cli/commands/config.py +4 -4
- mcp_agent/cli/commands/setup.py +11 -26
- mcp_agent/cli/main.py +6 -9
- mcp_agent/cli/terminal.py +2 -2
- mcp_agent/config.py +1 -5
- mcp_agent/context.py +13 -26
- mcp_agent/context_dependent.py +3 -7
- mcp_agent/core/agent_app.py +46 -122
- mcp_agent/core/agent_types.py +29 -2
- mcp_agent/core/agent_utils.py +3 -5
- mcp_agent/core/decorators.py +6 -14
- mcp_agent/core/enhanced_prompt.py +25 -52
- mcp_agent/core/error_handling.py +1 -1
- mcp_agent/core/exceptions.py +8 -8
- mcp_agent/core/factory.py +30 -72
- mcp_agent/core/fastagent.py +48 -88
- mcp_agent/core/mcp_content.py +10 -19
- mcp_agent/core/prompt.py +8 -15
- mcp_agent/core/proxies.py +34 -25
- mcp_agent/core/request_params.py +46 -0
- mcp_agent/core/types.py +6 -6
- mcp_agent/core/validation.py +16 -16
- mcp_agent/executor/decorator_registry.py +11 -23
- mcp_agent/executor/executor.py +8 -17
- mcp_agent/executor/task_registry.py +2 -4
- mcp_agent/executor/temporal.py +28 -74
- mcp_agent/executor/workflow.py +3 -5
- mcp_agent/executor/workflow_signal.py +17 -29
- mcp_agent/human_input/handler.py +4 -9
- mcp_agent/human_input/types.py +2 -3
- mcp_agent/logging/events.py +1 -5
- mcp_agent/logging/json_serializer.py +7 -6
- mcp_agent/logging/listeners.py +20 -23
- mcp_agent/logging/logger.py +15 -17
- mcp_agent/logging/rich_progress.py +10 -8
- mcp_agent/logging/tracing.py +4 -6
- mcp_agent/logging/transport.py +24 -24
- mcp_agent/mcp/gen_client.py +4 -12
- mcp_agent/mcp/interfaces.py +107 -88
- mcp_agent/mcp/mcp_agent_client_session.py +11 -19
- mcp_agent/mcp/mcp_agent_server.py +8 -10
- mcp_agent/mcp/mcp_aggregator.py +49 -122
- mcp_agent/mcp/mcp_connection_manager.py +16 -37
- mcp_agent/mcp/prompt_message_multipart.py +12 -18
- mcp_agent/mcp/prompt_serialization.py +13 -38
- mcp_agent/mcp/prompts/prompt_load.py +99 -0
- mcp_agent/mcp/prompts/prompt_server.py +21 -128
- mcp_agent/mcp/prompts/prompt_template.py +20 -42
- mcp_agent/mcp/resource_utils.py +8 -17
- mcp_agent/mcp/sampling.py +62 -64
- mcp_agent/mcp/stdio.py +11 -8
- mcp_agent/mcp_server/__init__.py +1 -1
- mcp_agent/mcp_server/agent_server.py +10 -17
- mcp_agent/mcp_server_registry.py +13 -35
- mcp_agent/resources/examples/data-analysis/analysis-campaign.py +1 -1
- mcp_agent/resources/examples/data-analysis/analysis.py +1 -1
- mcp_agent/resources/examples/data-analysis/slides.py +110 -0
- mcp_agent/resources/examples/internal/agent.py +2 -1
- mcp_agent/resources/examples/internal/job.py +2 -1
- mcp_agent/resources/examples/internal/prompt_category.py +1 -1
- mcp_agent/resources/examples/internal/prompt_sizing.py +3 -5
- mcp_agent/resources/examples/internal/sizer.py +2 -1
- mcp_agent/resources/examples/internal/social.py +2 -1
- mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +1 -1
- mcp_agent/resources/examples/prompting/__init__.py +1 -1
- mcp_agent/resources/examples/prompting/agent.py +2 -1
- mcp_agent/resources/examples/prompting/image_server.py +5 -11
- mcp_agent/resources/examples/researcher/researcher-eval.py +1 -1
- mcp_agent/resources/examples/researcher/researcher-imp.py +3 -4
- mcp_agent/resources/examples/researcher/researcher.py +2 -1
- mcp_agent/resources/examples/workflows/agent_build.py +2 -1
- mcp_agent/resources/examples/workflows/chaining.py +2 -1
- mcp_agent/resources/examples/workflows/evaluator.py +2 -1
- mcp_agent/resources/examples/workflows/human_input.py +2 -1
- mcp_agent/resources/examples/workflows/orchestrator.py +2 -1
- mcp_agent/resources/examples/workflows/parallel.py +2 -1
- mcp_agent/resources/examples/workflows/router.py +2 -1
- mcp_agent/resources/examples/workflows/sse.py +1 -1
- mcp_agent/telemetry/usage_tracking.py +2 -1
- mcp_agent/ui/console_display.py +17 -41
- mcp_agent/workflows/embedding/embedding_base.py +1 -4
- mcp_agent/workflows/embedding/embedding_cohere.py +2 -2
- mcp_agent/workflows/embedding/embedding_openai.py +4 -13
- mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +23 -57
- mcp_agent/workflows/intent_classifier/intent_classifier_base.py +5 -8
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +7 -11
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +4 -8
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +4 -8
- mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +11 -22
- mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +3 -3
- mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +4 -6
- mcp_agent/workflows/llm/anthropic_utils.py +8 -29
- mcp_agent/workflows/llm/augmented_llm.py +94 -332
- mcp_agent/workflows/llm/augmented_llm_anthropic.py +43 -76
- mcp_agent/workflows/llm/augmented_llm_openai.py +46 -100
- mcp_agent/workflows/llm/augmented_llm_passthrough.py +42 -20
- mcp_agent/workflows/llm/augmented_llm_playback.py +8 -6
- mcp_agent/workflows/llm/memory.py +103 -0
- mcp_agent/workflows/llm/model_factory.py +9 -21
- mcp_agent/workflows/llm/openai_utils.py +1 -1
- mcp_agent/workflows/llm/prompt_utils.py +39 -27
- mcp_agent/workflows/llm/providers/multipart_converter_anthropic.py +246 -184
- mcp_agent/workflows/llm/providers/multipart_converter_openai.py +212 -202
- mcp_agent/workflows/llm/providers/openai_multipart.py +19 -61
- mcp_agent/workflows/llm/providers/sampling_converter_anthropic.py +11 -212
- mcp_agent/workflows/llm/providers/sampling_converter_openai.py +13 -215
- mcp_agent/workflows/llm/sampling_converter.py +117 -0
- mcp_agent/workflows/llm/sampling_format_converter.py +12 -29
- mcp_agent/workflows/orchestrator/orchestrator.py +24 -67
- mcp_agent/workflows/orchestrator/orchestrator_models.py +14 -40
- mcp_agent/workflows/parallel/fan_in.py +17 -47
- mcp_agent/workflows/parallel/fan_out.py +6 -12
- mcp_agent/workflows/parallel/parallel_llm.py +9 -26
- mcp_agent/workflows/router/router_base.py +29 -59
- mcp_agent/workflows/router/router_embedding.py +11 -25
- mcp_agent/workflows/router/router_embedding_cohere.py +2 -2
- mcp_agent/workflows/router/router_embedding_openai.py +2 -2
- mcp_agent/workflows/router/router_llm.py +12 -28
- mcp_agent/workflows/swarm/swarm.py +20 -48
- mcp_agent/workflows/swarm/swarm_anthropic.py +2 -2
- mcp_agent/workflows/swarm/swarm_openai.py +2 -2
- fast_agent_mcp-0.1.11.dist-info/RECORD +0 -160
- mcp_agent/workflows/llm/llm_selector.py +0 -345
- {fast_agent_mcp-0.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/licenses/LICENSE +0 -0
mcp_agent/mcp/resource_utils.py
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
import base64
|
2
2
|
from pathlib import Path
|
3
3
|
from typing import List, Optional, Tuple
|
4
|
+
|
4
5
|
from mcp.types import (
|
5
|
-
EmbeddedResource,
|
6
|
-
TextResourceContents,
|
7
6
|
BlobResourceContents,
|
7
|
+
EmbeddedResource,
|
8
8
|
ImageContent,
|
9
|
+
TextResourceContents,
|
9
10
|
)
|
10
11
|
from pydantic import AnyUrl
|
12
|
+
|
11
13
|
import mcp_agent.mcp.mime_utils as mime_utils
|
12
14
|
|
13
15
|
HTTP_TIMEOUT = 10 # Default timeout for HTTP requests
|
@@ -25,9 +27,7 @@ def find_resource_file(resource_path: str, prompt_files: List[Path]) -> Optional
|
|
25
27
|
return None
|
26
28
|
|
27
29
|
|
28
|
-
def load_resource_content(
|
29
|
-
resource_path: str, prompt_files: List[Path]
|
30
|
-
) -> ResourceContent:
|
30
|
+
def load_resource_content(resource_path: str, prompt_files: List[Path]) -> ResourceContent:
|
31
31
|
"""
|
32
32
|
Load a resource's content and determine its mime type
|
33
33
|
|
@@ -71,9 +71,6 @@ def create_resource_uri(path: str) -> str:
|
|
71
71
|
return f"resource://fast-agent/{Path(path).name}"
|
72
72
|
|
73
73
|
|
74
|
-
# Add this to your resource_utils.py module
|
75
|
-
|
76
|
-
|
77
74
|
def create_resource_reference(uri: str, mime_type: str) -> "EmbeddedResource":
|
78
75
|
"""
|
79
76
|
Create a reference to a resource without embedding its content directly.
|
@@ -101,9 +98,7 @@ def create_resource_reference(uri: str, mime_type: str) -> "EmbeddedResource":
|
|
101
98
|
return EmbeddedResource(type="resource", resource=resource_contents)
|
102
99
|
|
103
100
|
|
104
|
-
def create_embedded_resource(
|
105
|
-
resource_path: str, content: str, mime_type: str, is_binary: bool = False
|
106
|
-
) -> EmbeddedResource:
|
101
|
+
def create_embedded_resource(resource_path: str, content: str, mime_type: str, is_binary: bool = False) -> EmbeddedResource:
|
107
102
|
"""Create an embedded resource content object"""
|
108
103
|
# Format a valid resource URI string
|
109
104
|
resource_uri_str = create_resource_uri(resource_path)
|
@@ -141,9 +136,7 @@ def create_image_content(data: str, mime_type: str) -> ImageContent:
|
|
141
136
|
)
|
142
137
|
|
143
138
|
|
144
|
-
def create_blob_resource(
|
145
|
-
resource_path: str, content: str, mime_type: str
|
146
|
-
) -> EmbeddedResource:
|
139
|
+
def create_blob_resource(resource_path: str, content: str, mime_type: str) -> EmbeddedResource:
|
147
140
|
"""Create an embedded resource for binary data"""
|
148
141
|
return EmbeddedResource(
|
149
142
|
type="resource",
|
@@ -155,9 +148,7 @@ def create_blob_resource(
|
|
155
148
|
)
|
156
149
|
|
157
150
|
|
158
|
-
def create_text_resource(
|
159
|
-
resource_path: str, content: str, mime_type: str
|
160
|
-
) -> EmbeddedResource:
|
151
|
+
def create_text_resource(resource_path: str, content: str, mime_type: str) -> EmbeddedResource:
|
161
152
|
"""Create an embedded resource for text data"""
|
162
153
|
return EmbeddedResource(
|
163
154
|
type="resource",
|
mcp_agent/mcp/sampling.py
CHANGED
@@ -1,27 +1,22 @@
|
|
1
1
|
"""
|
2
|
-
|
3
|
-
This module is carefully designed to avoid circular imports in the agent system.
|
2
|
+
This simplified implementation directly converts between MCP types and PromptMessageMultipart.
|
4
3
|
"""
|
5
4
|
|
6
5
|
from mcp import ClientSession
|
7
6
|
from mcp.types import (
|
8
7
|
CreateMessageRequestParams,
|
9
8
|
CreateMessageResult,
|
10
|
-
TextContent,
|
11
9
|
)
|
12
10
|
|
11
|
+
from mcp_agent.core.agent_types import AgentConfig
|
13
12
|
from mcp_agent.logging.logger import get_logger
|
14
13
|
from mcp_agent.mcp.interfaces import AugmentedLLMProtocol
|
15
|
-
from mcp_agent.
|
16
|
-
|
17
|
-
# Protocol is sufficient to describe the interface - no need for TYPE_CHECKING imports
|
14
|
+
from mcp_agent.workflows.llm.sampling_converter import SamplingConverter
|
18
15
|
|
19
16
|
logger = get_logger(__name__)
|
20
17
|
|
21
18
|
|
22
|
-
def create_sampling_llm(
|
23
|
-
mcp_ctx: ClientSession, model_string: str
|
24
|
-
) -> AugmentedLLMProtocol:
|
19
|
+
def create_sampling_llm(params: CreateMessageRequestParams, model_string: str) -> AugmentedLLMProtocol:
|
25
20
|
"""
|
26
21
|
Create an LLM instance for sampling without tools support.
|
27
22
|
This utility function creates a minimal LLM instance based on the model string.
|
@@ -33,11 +28,9 @@ def create_sampling_llm(
|
|
33
28
|
Returns:
|
34
29
|
An initialized LLM instance ready to use
|
35
30
|
"""
|
31
|
+
from mcp_agent.agents.agent import Agent
|
36
32
|
from mcp_agent.workflows.llm.model_factory import ModelFactory
|
37
|
-
from mcp_agent.agents.agent import Agent, AgentConfig
|
38
33
|
|
39
|
-
# Get application context from global state if available
|
40
|
-
# We don't try to extract it from mcp_ctx as they're different contexts
|
41
34
|
app_context = None
|
42
35
|
try:
|
43
36
|
from mcp_agent.context import get_current_context
|
@@ -46,20 +39,10 @@ def create_sampling_llm(
|
|
46
39
|
except Exception:
|
47
40
|
logger.warning("App context not available for sampling call")
|
48
41
|
|
49
|
-
# Create a minimal agent configuration
|
50
|
-
agent_config = AgentConfig(
|
51
|
-
name="sampling_agent",
|
52
|
-
instruction="You are a sampling agent.",
|
53
|
-
servers=[], # No servers needed
|
54
|
-
)
|
55
|
-
|
56
|
-
# Create agent with our application context (not the MCP context)
|
57
|
-
# Set connection_persistence=False to avoid server connections
|
58
42
|
agent = Agent(
|
59
|
-
config=
|
43
|
+
config=sampling_agent_config(params),
|
60
44
|
context=app_context,
|
61
|
-
|
62
|
-
connection_persistence=False, # Avoid server connection management
|
45
|
+
connection_persistence=False,
|
63
46
|
)
|
64
47
|
|
65
48
|
# Create the LLM using the factory
|
@@ -72,13 +55,22 @@ def create_sampling_llm(
|
|
72
55
|
return llm
|
73
56
|
|
74
57
|
|
75
|
-
async def sample(
|
76
|
-
mcp_ctx: ClientSession, params: CreateMessageRequestParams
|
77
|
-
) -> CreateMessageResult:
|
58
|
+
async def sample(mcp_ctx: ClientSession, params: CreateMessageRequestParams) -> CreateMessageResult:
|
78
59
|
"""
|
79
|
-
Handle sampling requests from the MCP protocol.
|
80
|
-
|
81
|
-
|
60
|
+
Handle sampling requests from the MCP protocol using SamplingConverter.
|
61
|
+
|
62
|
+
This function:
|
63
|
+
1. Extracts the model from the request
|
64
|
+
2. Uses SamplingConverter to convert types
|
65
|
+
3. Calls the LLM's generate_prompt method
|
66
|
+
4. Returns the result as a CreateMessageResult
|
67
|
+
|
68
|
+
Args:
|
69
|
+
mcp_ctx: The MCP ClientSession
|
70
|
+
params: The sampling request parameters
|
71
|
+
|
72
|
+
Returns:
|
73
|
+
A CreateMessageResult containing the LLM's response
|
82
74
|
"""
|
83
75
|
model = None
|
84
76
|
try:
|
@@ -95,39 +87,45 @@ async def sample(
|
|
95
87
|
if model is None:
|
96
88
|
raise ValueError("No model configured")
|
97
89
|
|
98
|
-
# Create an LLM instance
|
99
|
-
llm = create_sampling_llm(
|
100
|
-
|
101
|
-
#
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
llm_response = f"Echo response: {user_message}"
|
118
|
-
|
119
|
-
# Return the LLM-generated response
|
120
|
-
return CreateMessageResult(
|
121
|
-
role="assistant",
|
122
|
-
content=TextContent(type="text", text=llm_response),
|
123
|
-
model=model,
|
124
|
-
stopReason="endTurn",
|
125
|
-
)
|
90
|
+
# Create an LLM instance
|
91
|
+
llm = create_sampling_llm(params, model)
|
92
|
+
|
93
|
+
# Extract all messages from the request params
|
94
|
+
if not params.messages:
|
95
|
+
raise ValueError("No messages provided")
|
96
|
+
|
97
|
+
# Convert all SamplingMessages to PromptMessageMultipart objects
|
98
|
+
conversation = SamplingConverter.convert_messages(params.messages)
|
99
|
+
|
100
|
+
# Extract request parameters using our converter
|
101
|
+
request_params = SamplingConverter.extract_request_params(params)
|
102
|
+
|
103
|
+
# Use the new public apply_prompt method which is cleaner than calling the protected method
|
104
|
+
llm_response = await llm.apply_prompt(conversation, request_params)
|
105
|
+
logger.info(f"Complete sampling request : {llm_response[:50]}...")
|
106
|
+
|
107
|
+
# Create result using our converter
|
108
|
+
return SamplingConverter.create_message_result(response=llm_response, model=model)
|
126
109
|
except Exception as e:
|
127
110
|
logger.error(f"Error in sampling: {str(e)}")
|
128
|
-
return
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
111
|
+
return SamplingConverter.error_result(error_message=f"Error in sampling: {str(e)}", model=model)
|
112
|
+
|
113
|
+
|
114
|
+
def sampling_agent_config(
|
115
|
+
params: CreateMessageRequestParams = None,
|
116
|
+
) -> AgentConfig:
|
117
|
+
"""
|
118
|
+
Build a sampling AgentConfig based on request parameters.
|
119
|
+
|
120
|
+
Args:
|
121
|
+
params: Optional CreateMessageRequestParams that may contain a system prompt
|
122
|
+
|
123
|
+
Returns:
|
124
|
+
An initialized AgentConfig for use in sampling
|
125
|
+
"""
|
126
|
+
# Use systemPrompt from params if available, otherwise use default
|
127
|
+
instruction = "You are a helpful AI Agent."
|
128
|
+
if params and hasattr(params, "systemPrompt") and params.systemPrompt is not None:
|
129
|
+
instruction = params.systemPrompt
|
130
|
+
|
131
|
+
return AgentConfig(name="sampling_agent", instruction=instruction, servers=[])
|
mcp_agent/mcp/stdio.py
CHANGED
@@ -2,14 +2,19 @@
|
|
2
2
|
Custom implementation of stdio_client that handles stderr through rich console.
|
3
3
|
"""
|
4
4
|
|
5
|
-
from contextlib import asynccontextmanager
|
6
5
|
import subprocess
|
6
|
+
from contextlib import asynccontextmanager
|
7
|
+
from typing import TYPE_CHECKING
|
8
|
+
|
7
9
|
import anyio
|
10
|
+
import mcp.types as types
|
8
11
|
from anyio.streams.text import TextReceiveStream
|
9
12
|
from mcp.client.stdio import StdioServerParameters, get_default_environment
|
10
|
-
|
13
|
+
|
11
14
|
from mcp_agent.logging.logger import get_logger
|
12
|
-
|
15
|
+
|
16
|
+
if TYPE_CHECKING:
|
17
|
+
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
13
18
|
|
14
19
|
logger = get_logger(__name__)
|
15
20
|
|
@@ -45,11 +50,9 @@ async def stdio_client_with_rich_stderr(server: StdioServerParameters):
|
|
45
50
|
|
46
51
|
if process.returncode is not None:
|
47
52
|
logger.debug(f"return code (early){process.returncode}")
|
48
|
-
raise RuntimeError(
|
49
|
-
f"Process terminated immediately with code {process.returncode}"
|
50
|
-
)
|
53
|
+
raise RuntimeError(f"Process terminated immediately with code {process.returncode}")
|
51
54
|
|
52
|
-
async def stdout_reader():
|
55
|
+
async def stdout_reader() -> None:
|
53
56
|
assert process.stdout, "Opened process is missing stdout"
|
54
57
|
try:
|
55
58
|
async with read_stream_writer:
|
@@ -89,7 +92,7 @@ async def stdio_client_with_rich_stderr(server: StdioServerParameters):
|
|
89
92
|
# except anyio.ClosedResourceError:
|
90
93
|
# await anyio.lowlevel.checkpoint()
|
91
94
|
|
92
|
-
async def stdin_writer():
|
95
|
+
async def stdin_writer() -> None:
|
93
96
|
assert process.stdin, "Opened process is missing stdin"
|
94
97
|
try:
|
95
98
|
async with write_stream_reader:
|
mcp_agent/mcp_server/__init__.py
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# src/mcp_agent/mcp_server/agent_server.py
|
2
2
|
|
3
|
+
from mcp.server.fastmcp import Context as MCPContext
|
3
4
|
from mcp.server.fastmcp import FastMCP
|
4
5
|
|
5
6
|
# Remove circular import
|
6
7
|
from mcp_agent.core.agent_app import AgentApp
|
7
|
-
from mcp.server.fastmcp import Context as MCPContext
|
8
8
|
|
9
9
|
|
10
10
|
class AgentMCPServer:
|
@@ -15,21 +15,20 @@ class AgentMCPServer:
|
|
15
15
|
agent_app: AgentApp,
|
16
16
|
server_name: str = "FastAgent-MCP-Server",
|
17
17
|
server_description: str = None,
|
18
|
-
):
|
18
|
+
) -> None:
|
19
19
|
self.agent_app = agent_app
|
20
20
|
self.mcp_server = FastMCP(
|
21
21
|
name=server_name,
|
22
|
-
instructions=server_description
|
23
|
-
or f"This server provides access to {len(agent_app.agents)} agents",
|
22
|
+
instructions=server_description or f"This server provides access to {len(agent_app.agents)} agents",
|
24
23
|
)
|
25
24
|
self.setup_tools()
|
26
25
|
|
27
|
-
def setup_tools(self):
|
26
|
+
def setup_tools(self) -> None:
|
28
27
|
"""Register all agents as MCP tools."""
|
29
28
|
for agent_name, agent_proxy in self.agent_app._agents.items():
|
30
29
|
self.register_agent_tools(agent_name, agent_proxy)
|
31
30
|
|
32
|
-
def register_agent_tools(self, agent_name: str, agent_proxy):
|
31
|
+
def register_agent_tools(self, agent_name: str, agent_proxy) -> None:
|
33
32
|
"""Register tools for a specific agent."""
|
34
33
|
|
35
34
|
# Basic send message tool
|
@@ -42,9 +41,7 @@ class AgentMCPServer:
|
|
42
41
|
|
43
42
|
# Get the agent's context
|
44
43
|
agent_context = None
|
45
|
-
if hasattr(agent_proxy, "_agent") and hasattr(
|
46
|
-
agent_proxy._agent, "context"
|
47
|
-
):
|
44
|
+
if hasattr(agent_proxy, "_agent") and hasattr(agent_proxy._agent, "context"):
|
48
45
|
agent_context = agent_proxy._agent.context
|
49
46
|
|
50
47
|
# Define the function to execute
|
@@ -57,7 +54,7 @@ class AgentMCPServer:
|
|
57
54
|
else:
|
58
55
|
return await execute_send()
|
59
56
|
|
60
|
-
def run(self, transport: str = "sse", host: str = "0.0.0.0", port: int = 8000):
|
57
|
+
def run(self, transport: str = "sse", host: str = "0.0.0.0", port: int = 8000) -> None:
|
61
58
|
"""Run the MCP server."""
|
62
59
|
if transport == "sse":
|
63
60
|
# For running as a web server
|
@@ -66,9 +63,7 @@ class AgentMCPServer:
|
|
66
63
|
|
67
64
|
self.mcp_server.run(transport=transport)
|
68
65
|
|
69
|
-
async def run_async(
|
70
|
-
self, transport: str = "sse", host: str = "0.0.0.0", port: int = 8000
|
71
|
-
):
|
66
|
+
async def run_async(self, transport: str = "sse", host: str = "0.0.0.0", port: int = 8000) -> None:
|
72
67
|
"""Run the MCP server asynchronously."""
|
73
68
|
if transport == "sse":
|
74
69
|
self.mcp_server.settings.host = host
|
@@ -77,9 +72,7 @@ class AgentMCPServer:
|
|
77
72
|
else: # stdio
|
78
73
|
await self.mcp_server.run_stdio_async()
|
79
74
|
|
80
|
-
async def with_bridged_context(
|
81
|
-
self, agent_context, mcp_context, func, *args, **kwargs
|
82
|
-
):
|
75
|
+
async def with_bridged_context(self, agent_context, mcp_context, func, *args, **kwargs):
|
83
76
|
"""
|
84
77
|
Execute a function with bridged context between MCP and agent
|
85
78
|
|
@@ -98,7 +91,7 @@ class AgentMCPServer:
|
|
98
91
|
agent_context.mcp_context = mcp_context
|
99
92
|
|
100
93
|
# Create bridged progress reporter
|
101
|
-
async def bridged_progress(progress, total=None):
|
94
|
+
async def bridged_progress(progress, total=None) -> None:
|
102
95
|
if mcp_context:
|
103
96
|
await mcp_context.report_progress(progress, total)
|
104
97
|
if original_progress_reporter:
|
mcp_agent/mcp_server_registry.py
CHANGED
@@ -9,25 +9,25 @@ server initialization.
|
|
9
9
|
|
10
10
|
from contextlib import asynccontextmanager
|
11
11
|
from datetime import timedelta
|
12
|
-
from typing import Callable, Dict
|
12
|
+
from typing import AsyncGenerator, Callable, Dict
|
13
13
|
|
14
14
|
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
15
15
|
from mcp import ClientSession
|
16
|
+
from mcp.client.sse import sse_client
|
16
17
|
from mcp.client.stdio import (
|
17
18
|
StdioServerParameters,
|
18
19
|
get_default_environment,
|
19
20
|
)
|
20
|
-
from mcp_agent.mcp.stdio import stdio_client_with_rich_stderr
|
21
|
-
from mcp.client.sse import sse_client
|
22
21
|
|
23
22
|
from mcp_agent.config import (
|
24
|
-
get_settings,
|
25
23
|
MCPServerAuthSettings,
|
26
24
|
MCPServerSettings,
|
27
25
|
Settings,
|
26
|
+
get_settings,
|
28
27
|
)
|
29
28
|
from mcp_agent.logging.logger import get_logger
|
30
29
|
from mcp_agent.mcp.mcp_connection_manager import MCPConnectionManager
|
30
|
+
from mcp_agent.mcp.stdio import stdio_client_with_rich_stderr
|
31
31
|
|
32
32
|
logger = get_logger(__name__)
|
33
33
|
|
@@ -58,7 +58,7 @@ class ServerRegistry:
|
|
58
58
|
init_hooks (Dict[str, InitHookCallable]): Registered initialization hooks.
|
59
59
|
"""
|
60
60
|
|
61
|
-
def __init__(self, config: Settings | None = None, config_path: str | None = None):
|
61
|
+
def __init__(self, config: Settings | None = None, config_path: str | None = None) -> None:
|
62
62
|
"""
|
63
63
|
Initialize the ServerRegistry with a configuration file.
|
64
64
|
|
@@ -66,17 +66,11 @@ class ServerRegistry:
|
|
66
66
|
config (Settings): The Settings object containing the server configurations.
|
67
67
|
config_path (str): Path to the YAML configuration file.
|
68
68
|
"""
|
69
|
-
self.registry = (
|
70
|
-
self.load_registry_from_file(config_path)
|
71
|
-
if config is None
|
72
|
-
else config.mcp.servers
|
73
|
-
)
|
69
|
+
self.registry = self.load_registry_from_file(config_path) if config is None else config.mcp.servers
|
74
70
|
self.init_hooks: Dict[str, InitHookCallable] = {}
|
75
71
|
self.connection_manager = MCPConnectionManager(self)
|
76
72
|
|
77
|
-
def load_registry_from_file(
|
78
|
-
self, config_path: str | None = None
|
79
|
-
) -> Dict[str, MCPServerSettings]:
|
73
|
+
def load_registry_from_file(self, config_path: str | None = None) -> Dict[str, MCPServerSettings]:
|
80
74
|
"""
|
81
75
|
Load the YAML configuration file and validate it.
|
82
76
|
|
@@ -116,17 +110,11 @@ class ServerRegistry:
|
|
116
110
|
|
117
111
|
config = self.registry[server_name]
|
118
112
|
|
119
|
-
read_timeout_seconds = (
|
120
|
-
timedelta(config.read_timeout_seconds)
|
121
|
-
if config.read_timeout_seconds
|
122
|
-
else None
|
123
|
-
)
|
113
|
+
read_timeout_seconds = timedelta(config.read_timeout_seconds) if config.read_timeout_seconds else None
|
124
114
|
|
125
115
|
if config.transport == "stdio":
|
126
116
|
if not config.command or not config.args:
|
127
|
-
raise ValueError(
|
128
|
-
f"Command and args are required for stdio transport: {server_name}"
|
129
|
-
)
|
117
|
+
raise ValueError(f"Command and args are required for stdio transport: {server_name}")
|
130
118
|
|
131
119
|
server_params = StdioServerParameters(
|
132
120
|
command=config.command,
|
@@ -144,9 +132,7 @@ class ServerRegistry:
|
|
144
132
|
read_timeout_seconds,
|
145
133
|
)
|
146
134
|
async with session:
|
147
|
-
logger.info(
|
148
|
-
f"{server_name}: Connected to server using stdio transport."
|
149
|
-
)
|
135
|
+
logger.info(f"{server_name}: Connected to server using stdio transport.")
|
150
136
|
try:
|
151
137
|
yield session
|
152
138
|
finally:
|
@@ -164,9 +150,7 @@ class ServerRegistry:
|
|
164
150
|
read_timeout_seconds,
|
165
151
|
)
|
166
152
|
async with session:
|
167
|
-
logger.info(
|
168
|
-
f"{server_name}: Connected to server using SSE transport."
|
169
|
-
)
|
153
|
+
logger.info(f"{server_name}: Connected to server using SSE transport.")
|
170
154
|
try:
|
171
155
|
yield session
|
172
156
|
finally:
|
@@ -206,19 +190,13 @@ class ServerRegistry:
|
|
206
190
|
|
207
191
|
config = self.registry[server_name]
|
208
192
|
|
209
|
-
async with self.start_server(
|
210
|
-
server_name, client_session_factory=client_session_factory
|
211
|
-
) as session:
|
193
|
+
async with self.start_server(server_name, client_session_factory=client_session_factory) as session:
|
212
194
|
try:
|
213
195
|
logger.info(f"{server_name}: Initializing server...")
|
214
196
|
await session.initialize()
|
215
197
|
logger.info(f"{server_name}: Initialized.")
|
216
198
|
|
217
|
-
intialization_callback = (
|
218
|
-
init_hook
|
219
|
-
if init_hook is not None
|
220
|
-
else self.init_hooks.get(server_name)
|
221
|
-
)
|
199
|
+
intialization_callback = init_hook if init_hook is not None else self.init_hooks.get(server_name)
|
222
200
|
|
223
201
|
if intialization_callback:
|
224
202
|
logger.info(f"{server_name}: Executing init hook")
|
@@ -172,7 +172,7 @@ Extract key insights that would be compelling for a social media campaign.
|
|
172
172
|
request_params=RequestParams(maxTokens=8192),
|
173
173
|
plan_type="full",
|
174
174
|
)
|
175
|
-
async def main():
|
175
|
+
async def main() -> None:
|
176
176
|
# Use the app's context manager
|
177
177
|
print(
|
178
178
|
"WARNING: This workflow will likely run for >10 minutes and consume a lot of tokens. Press Enter to accept the default prompt and proceed"
|
@@ -23,7 +23,7 @@ Visualisations should be saved as .png files in the current working directory.
|
|
23
23
|
servers=["interpreter"],
|
24
24
|
request_params=RequestParams(maxTokens=8192),
|
25
25
|
)
|
26
|
-
async def main():
|
26
|
+
async def main() -> None:
|
27
27
|
# Use the app's context manager
|
28
28
|
async with fast.run() as agent:
|
29
29
|
await agent(
|
@@ -0,0 +1,110 @@
|
|
1
|
+
import asyncio
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import TYPE_CHECKING
|
4
|
+
|
5
|
+
from mcp_agent.core.fastagent import FastAgent
|
6
|
+
from mcp_agent.mcp.prompts.prompt_load import load_prompt_multipart
|
7
|
+
from mcp_agent.workflows.llm.augmented_llm import RequestParams
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
|
11
|
+
|
12
|
+
# Create the application
|
13
|
+
fast = FastAgent("Data Analysis (Roots)")
|
14
|
+
|
15
|
+
|
16
|
+
# The sample data is under Database Contents License (DbCL) v1.0.
|
17
|
+
|
18
|
+
# Available here : https://www.kaggle.com/datasets/pavansubhasht/ibm-hr-analytics-attrition-dataset
|
19
|
+
|
20
|
+
# The CSS files are distributed under the MIT License from the excellent
|
21
|
+
# marpstyle project : https://github.com/cunhapaulo/marpstyle
|
22
|
+
|
23
|
+
|
24
|
+
@fast.agent(
|
25
|
+
name="slides",
|
26
|
+
servers=["filesystem"],
|
27
|
+
instruction="""
|
28
|
+
You produce compelling slide decks for impactful presentations. You usually try and keep the pack to between
|
29
|
+
8-12 slides, with key insights at the start, backed up with data, diagrams and analysis as available. You
|
30
|
+
are able to help direct colour, style and and questions for enhancing the presentation. Produced charts and
|
31
|
+
visualisations will be in the ./mount-point/ directory. You output MARP markdown files.
|
32
|
+
""",
|
33
|
+
request_params=RequestParams(maxTokens=8192),
|
34
|
+
)
|
35
|
+
@fast.agent(
|
36
|
+
name="data_analysis",
|
37
|
+
instruction="""
|
38
|
+
You have access to a Python 3.12 interpreter and you can use this to analyse and process data.
|
39
|
+
Common analysis packages such as Pandas, Seaborn and Matplotlib are already installed.
|
40
|
+
You can add further packages if needed.
|
41
|
+
Data files are accessible from the /mnt/data/ directory (this is the current working directory).
|
42
|
+
Visualisations should be saved as .png files in the current working directory.
|
43
|
+
""",
|
44
|
+
servers=["interpreter"],
|
45
|
+
request_params=RequestParams(maxTokens=8192),
|
46
|
+
)
|
47
|
+
@fast.orchestrator(
|
48
|
+
name="orchestrator",
|
49
|
+
plan_type="iterative",
|
50
|
+
agents=["slides", "data_analysis"],
|
51
|
+
)
|
52
|
+
async def main() -> None:
|
53
|
+
# Use the app's context manager
|
54
|
+
async with fast.run() as agent:
|
55
|
+
prompts: list[PromptMessageMultipart] = load_prompt_multipart(Path("slides.md"))
|
56
|
+
await agent.slides.apply_prompt_messages(prompts)
|
57
|
+
|
58
|
+
await agent.orchestrator.send(
|
59
|
+
"Produce a compelling presentation for the CSV data file in the /mnt/data/ directory."
|
60
|
+
"The slides agent will produce a presentation, make sure to consult it first for "
|
61
|
+
"colour scheme and formatting guidance. Make sure that any 'call-outs' have a distinct"
|
62
|
+
"background to ensure they stand out."
|
63
|
+
"Make sure the presentation is impactful, concise and visualises key insights in to the data"
|
64
|
+
" in a compelling way."
|
65
|
+
"The presentation is by the 'llmindset team' and produced in 'march 2025'."
|
66
|
+
"Produce it step-by-step; long responses without checking in are likely to exceed"
|
67
|
+
"maximum output token limits."
|
68
|
+
)
|
69
|
+
# colours: str = await agent.slides.send("Tell the Data Analysis agent what colour schemes and chart sizes you prefer for the presentation")
|
70
|
+
|
71
|
+
# analysis: str = await agent.data_analysis.send(
|
72
|
+
# "Examine the CSV file in /mnt/data, produce a detailed analysis of the data,"
|
73
|
+
# "and any patterns it contains. Visualise some of the key points, saving .png files to"
|
74
|
+
# "your current workig folder (/mnt/data). Respond with a summary of your findings, and a list"
|
75
|
+
# "of visualiations and their filenames ready to incorporate in to a slide deck. The presentation agent has"
|
76
|
+
# f"specified the following style guidelines for generated charts:\n {colours}"
|
77
|
+
# )
|
78
|
+
# await agent.slides.send(
|
79
|
+
# "Produce a MARP Presentation for the this analysis. You will find the visualisations in "
|
80
|
+
# f"in the ./mount-point/ folder. The analysis follows....\n{analysis}"
|
81
|
+
# )
|
82
|
+
|
83
|
+
await agent()
|
84
|
+
|
85
|
+
|
86
|
+
if __name__ == "__main__":
|
87
|
+
asyncio.run(main())
|
88
|
+
|
89
|
+
|
90
|
+
############################################################################################################
|
91
|
+
# Example of evaluator/optimizer flow
|
92
|
+
############################################################################################################
|
93
|
+
# @fast.agent(
|
94
|
+
# "evaluator",
|
95
|
+
# """You are collaborating with a Data Analysis tool that has the capability to analyse data and produce visualisations.
|
96
|
+
# You must make sure that the tool has:
|
97
|
+
# - Considered the best way for a Human to interpret the data
|
98
|
+
# - Produced insightful visualasions.
|
99
|
+
# - Provided a high level summary report for the Human.
|
100
|
+
# - Has had its findings challenged, and justified
|
101
|
+
# """,
|
102
|
+
# request_params=RequestParams(maxTokens=8192),
|
103
|
+
# )
|
104
|
+
# @fast.evaluator_optimizer(
|
105
|
+
# "analysis_tool",
|
106
|
+
# generator="data_analysis",
|
107
|
+
# evaluator="evaluator",
|
108
|
+
# max_refinements=3,
|
109
|
+
# min_rating="EXCELLENT",
|
110
|
+
# )
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import asyncio
|
2
|
+
|
2
3
|
from mcp_agent.core.fastagent import FastAgent
|
3
4
|
|
4
5
|
# Create the application
|
@@ -7,7 +8,7 @@ fast = FastAgent("FastAgent Example")
|
|
7
8
|
|
8
9
|
# Define the agent
|
9
10
|
@fast.agent(servers=["fetch", "mcp_hfspace"])
|
10
|
-
async def main():
|
11
|
+
async def main() -> None:
|
11
12
|
# use the --model command line switch or agent arguments to change model
|
12
13
|
async with fast.run() as agent:
|
13
14
|
await agent.prompt()
|
@@ -5,6 +5,7 @@ PMO Job Description Generator Agent
|
|
5
5
|
"""
|
6
6
|
|
7
7
|
import asyncio
|
8
|
+
|
8
9
|
from mcp_agent.core.fastagent import FastAgent
|
9
10
|
|
10
11
|
# Create the application
|
@@ -57,7 +58,7 @@ fast = FastAgent("PMO Job Description Generator")
|
|
57
58
|
min_rating="EXCELLENT",
|
58
59
|
max_refinements=2,
|
59
60
|
)
|
60
|
-
async def main():
|
61
|
+
async def main() -> None:
|
61
62
|
async with fast.run() as agent:
|
62
63
|
roles = [
|
63
64
|
"PMO Director",
|