fast-agent-mcp 0.2.4__py3-none-any.whl → 0.2.5__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.4.dist-info → fast_agent_mcp-0.2.5.dist-info}/METADATA +2 -2
- {fast_agent_mcp-0.2.4.dist-info → fast_agent_mcp-0.2.5.dist-info}/RECORD +20 -21
- mcp_agent/agents/base_agent.py +13 -13
- mcp_agent/cli/main.py +11 -0
- mcp_agent/config.py +29 -7
- mcp_agent/context.py +2 -0
- mcp_agent/core/direct_factory.py +5 -14
- mcp_agent/core/enhanced_prompt.py +3 -3
- mcp_agent/core/fastagent.py +202 -46
- mcp_agent/llm/augmented_llm.py +7 -9
- mcp_agent/llm/augmented_llm_passthrough.py +3 -1
- mcp_agent/llm/model_factory.py +5 -7
- mcp_agent/mcp/prompt_serialization.py +42 -0
- mcp_agent/mcp/prompts/prompt_load.py +51 -3
- mcp_agent/mcp_server/agent_server.py +60 -11
- mcp_agent/resources/examples/internal/agent.py +2 -2
- mcp_agent/resources/examples/internal/fastagent.config.yaml +5 -0
- mcp_agent/mcp/mcp_agent_server.py +0 -56
- {fast_agent_mcp-0.2.4.dist-info → fast_agent_mcp-0.2.5.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.4.dist-info → fast_agent_mcp-0.2.5.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.2.4.dist-info → fast_agent_mcp-0.2.5.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.5
|
4
4
|
Summary: Define, Prompt and Test MCP enabled Agents and Workflows
|
5
5
|
Author-email: Shaun Smith <fastagent@llmindset.co.uk>, Sarmad Qadri <sarmad@lastmileai.dev>
|
6
6
|
License: Apache License
|
@@ -244,7 +244,7 @@ Description-Content-Type: text/markdown
|
|
244
244
|
<a href="https://pypi.org/project/fast-agent-mcp/"><img src="https://img.shields.io/pypi/v/fast-agent-mcp?color=%2334D058&label=pypi" /></a>
|
245
245
|
<a href="#"><img src="https://github.com/evalstate/fast-agent/actions/workflows/main-checks.yml/badge.svg" /></a>
|
246
246
|
<a href="https://github.com/evalstate/fast-agent/issues"><img src="https://img.shields.io/github/issues-raw/evalstate/fast-agent" /></a>
|
247
|
-
<a href="https://
|
247
|
+
<a href="https://discord.gg/xg5cJ7ndN6"><img src="https://img.shields.io/discord/1358470293990936787" alt="discord" /></a>
|
248
248
|
<img alt="Pepy Total Downloads" src="https://img.shields.io/pepy/dt/fast-agent-mcp?label=pypi%20%7C%20downloads"/>
|
249
249
|
<a href="https://github.com/evalstate/fast-agent-mcp/blob/main/LICENSE"><img src="https://img.shields.io/pypi/l/fast-agent-mcp" /></a>
|
250
250
|
</p>
|
@@ -1,15 +1,15 @@
|
|
1
1
|
mcp_agent/__init__.py,sha256=-AIoeL4c9UAp_P4U0z-uIWTTmQWdihOis5nbQ5L_eao,1664
|
2
2
|
mcp_agent/app.py,sha256=jBmzYM_o50g8vhlTgkkf5TGiBWNbXWViYnd0WANbpzo,10276
|
3
|
-
mcp_agent/config.py,sha256=
|
3
|
+
mcp_agent/config.py,sha256=V6TZlKOUelv5N75fypWKFVvkY5YsgpoHrdiSsKxOiM0,11725
|
4
4
|
mcp_agent/console.py,sha256=Gjf2QLFumwG1Lav__c07X_kZxxEUSkzV-1_-YbAwcwo,813
|
5
|
-
mcp_agent/context.py,sha256=
|
5
|
+
mcp_agent/context.py,sha256=pp_F1Q1jgAxGrRccSZJutn1JUxYfVue-St3S8tUyptM,7903
|
6
6
|
mcp_agent/context_dependent.py,sha256=QXfhw3RaQCKfscEEBRGuZ3sdMWqkgShz2jJ1ivGGX1I,1455
|
7
7
|
mcp_agent/event_progress.py,sha256=25iz0yyg-O4glMmtijcYpDdUmtUIKsCmR_8A52GgeC4,2716
|
8
8
|
mcp_agent/mcp_server_registry.py,sha256=r24xX4BYXj4BbWbU37uwuW9e1mFOYgpb258OMb21SaY,9928
|
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=NKz8HTCdjIBDSJwl6EHU2NDnZcAtYiaYH3YnbVGrc3Q,3882
|
12
|
-
mcp_agent/agents/base_agent.py,sha256=
|
12
|
+
mcp_agent/agents/base_agent.py,sha256=mhLgsS_pST1LeBZmnBo9ldEthcKV9LZdBSdt5PkafCk,23184
|
13
13
|
mcp_agent/agents/workflow/__init__.py,sha256=HloteEW6kalvgR0XewpiFAqaQlMPlPJYg5p3K33IUzI,25
|
14
14
|
mcp_agent/agents/workflow/chain_agent.py,sha256=ff5ksaJiAm007MMl8QO4pBTTIgQLcf9GLZpRtYkfBJQ,6201
|
15
15
|
mcp_agent/agents/workflow/evaluator_optimizer.py,sha256=ArM2CySsTY0gSPndox1DdjecRdNtWIj-Qm9ApUWkygw,13103
|
@@ -20,7 +20,7 @@ mcp_agent/agents/workflow/parallel_agent.py,sha256=denkFKrvZJZ4c3Cja9cx-EOUhd-mD
|
|
20
20
|
mcp_agent/agents/workflow/router_agent.py,sha256=iOIPhMP9-w-lho50obx1rvGphI_-7Hdr9E9ohlKmtJk,10484
|
21
21
|
mcp_agent/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
22
|
mcp_agent/cli/__main__.py,sha256=AVZ7tQFhU_sDOGuUGJq8ujgKtcxsYJBJwHbVaaiRDlI,166
|
23
|
-
mcp_agent/cli/main.py,sha256=
|
23
|
+
mcp_agent/cli/main.py,sha256=PZdPJfsAJOm80vTu7j_XpMPhaDZOpqSe-ciU3YQsmA4,3149
|
24
24
|
mcp_agent/cli/terminal.py,sha256=GRwD-RGW7saIz2IOWZn5vD6JjiArscELBThm1GTFkuI,1065
|
25
25
|
mcp_agent/cli/commands/bootstrap.py,sha256=Pv3LQUQLK_5-8nbOQ6iibJI7awgD04P9xh6-VpU15pw,11571
|
26
26
|
mcp_agent/cli/commands/config.py,sha256=jU2gl4d5YESrdUboh3u6mxf7CxVT-_DT_sK8Vuh3ajw,231
|
@@ -29,11 +29,11 @@ mcp_agent/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
29
|
mcp_agent/core/agent_app.py,sha256=9c7V-gZKudl-6u2BB__aLEvC4iemJeWpyoI8fGpXsXk,10145
|
30
30
|
mcp_agent/core/agent_types.py,sha256=Qyhvzy2CcD7wMaxavuMUOQnD_rg5LZ1RT3DwXVYaM1Q,1345
|
31
31
|
mcp_agent/core/direct_decorators.py,sha256=_kS0C9UbwRQ54z58vfUapFXXyJrm1XRVyLMuB7bL0b8,14471
|
32
|
-
mcp_agent/core/direct_factory.py,sha256=
|
33
|
-
mcp_agent/core/enhanced_prompt.py,sha256=
|
32
|
+
mcp_agent/core/direct_factory.py,sha256=oMozbr6kK37vWtAIvzCKtlX1i7KTez3hLdQfeXCWUjM,17580
|
33
|
+
mcp_agent/core/enhanced_prompt.py,sha256=P9FAtc0rqIYQfUDkTNVXitFIZEtB3fdq_Nr0-st64Qg,17948
|
34
34
|
mcp_agent/core/error_handling.py,sha256=xoyS2kLe0eG0bj2eSJCJ2odIhGUve2SbDR7jP-A-uRw,624
|
35
35
|
mcp_agent/core/exceptions.py,sha256=ENAD_qGG67foxy6vDkIvc-lgopIUQy6O7zvNPpPXaQg,2289
|
36
|
-
mcp_agent/core/fastagent.py,sha256=
|
36
|
+
mcp_agent/core/fastagent.py,sha256=HTy1OCAhpHIM-4cd37-dxvb97eZUELN-ICAEFgqmJMk,18503
|
37
37
|
mcp_agent/core/interactive_prompt.py,sha256=04yoeOX2JLatr2tuOFfnb84GMwFUIBnBC7y1M_gqOM8,17692
|
38
38
|
mcp_agent/core/mcp_content.py,sha256=2D7KHY9mG_vxoDwFLKvsPQV9VRIzHItM7V-jcEnACh8,8878
|
39
39
|
mcp_agent/core/prompt.py,sha256=qnintOUGEoDPYLI9bu9G2OlgVMCe5ZPUZilgMzydXhc,7919
|
@@ -50,11 +50,11 @@ mcp_agent/human_input/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
|
|
50
50
|
mcp_agent/human_input/handler.py,sha256=s712Z5ssTCwjL9-VKoIdP5CtgMh43YvepynYisiWTTA,3144
|
51
51
|
mcp_agent/human_input/types.py,sha256=RtWBOVzy8vnYoQrc36jRLn8z8N3C4pDPMBN5vF6qM5Y,1476
|
52
52
|
mcp_agent/llm/__init__.py,sha256=d8zgwG-bRFuwiMNMYkywg_qytk4P8lawyld_meuUmHI,68
|
53
|
-
mcp_agent/llm/augmented_llm.py,sha256=
|
54
|
-
mcp_agent/llm/augmented_llm_passthrough.py,sha256=
|
53
|
+
mcp_agent/llm/augmented_llm.py,sha256=K7sfUaJrFqupEbq7xi7hDjyAC4UMXQBYQxza5TFJ6VA,18117
|
54
|
+
mcp_agent/llm/augmented_llm_passthrough.py,sha256=U0LssNWNVuZRuD9I7Wuvpo7vdDW4xtoPLirnYCgBGTY,6128
|
55
55
|
mcp_agent/llm/augmented_llm_playback.py,sha256=YVR2adzjMf9Q5WfYBytryWMRqJ87a3kNBnjxhApsMcU,3413
|
56
56
|
mcp_agent/llm/memory.py,sha256=UakoBCJBf59JBtB6uyZM0OZjlxDW_VHtSfDs08ibVEc,3312
|
57
|
-
mcp_agent/llm/model_factory.py,sha256=
|
57
|
+
mcp_agent/llm/model_factory.py,sha256=WOm2IkZLhgFTplmgjVAzNzjpawm3D_aTVcGdgu1DReg,7606
|
58
58
|
mcp_agent/llm/prompt_utils.py,sha256=yWQHykoK13QRF7evHUKxVF0SpVLN-Bsft0Yixzvn0g0,4825
|
59
59
|
mcp_agent/llm/sampling_converter.py,sha256=C7wPBlmT0eD90XWabC22zkxsrVHKCrjwIwg6cG628cI,2926
|
60
60
|
mcp_agent/llm/sampling_format_converter.py,sha256=xGz4odHpOcP7--eFaJaFtUR8eR9jxZS7MnLH6J7n0EU,1263
|
@@ -84,13 +84,12 @@ mcp_agent/mcp/interfaces.py,sha256=8fsQj8r7sMrIyrJHHbUIEP86SVLyXPpEI36KZq1abc0,6
|
|
84
84
|
mcp_agent/mcp/logger_textio.py,sha256=OpnqMam9Pu0oVzYQWFMhrX1dRg2f5Fqb3qqPA6QAATM,2778
|
85
85
|
mcp_agent/mcp/mcp_activity.py,sha256=CajXCFWZ2cKEX9s4-HfNVAj471ePTVs4NOkvmIh65tE,592
|
86
86
|
mcp_agent/mcp/mcp_agent_client_session.py,sha256=RMYNltc2pDIzxwEJSS5589RbvPO0KWV4Y3jSyAmhKf0,4181
|
87
|
-
mcp_agent/mcp/mcp_agent_server.py,sha256=SnKJ9KCMnklGLHKZ9UsgAxd9IOKqiSCRPmQeTGu0CK8,1643
|
88
87
|
mcp_agent/mcp/mcp_aggregator.py,sha256=jaWbOvb3wioECohZ47CubyxfJ5QkfNSshu1hwhZksG4,40486
|
89
88
|
mcp_agent/mcp/mcp_connection_manager.py,sha256=desQBreHbIcjY7AidcDO6pFomHOx9oOZPOWIcHAx1K0,13761
|
90
89
|
mcp_agent/mcp/mime_utils.py,sha256=difepNR_gpb4MpMLkBRAoyhDk-AjXUHTiqKvT_VwS1o,1805
|
91
90
|
mcp_agent/mcp/prompt_message_multipart.py,sha256=IpIndd75tAcCbJbfqjpAF0tOUUP1TQceDbWoxO5gvpo,3684
|
92
91
|
mcp_agent/mcp/prompt_render.py,sha256=k3v4BZDThGE2gGiOYVQtA6x8WTEdOuXIEnRafANhN1U,2996
|
93
|
-
mcp_agent/mcp/prompt_serialization.py,sha256
|
92
|
+
mcp_agent/mcp/prompt_serialization.py,sha256=El12k47c9568sju6fqiaYkYySC9RVhScarHeR_QihNk,17323
|
94
93
|
mcp_agent/mcp/resource_utils.py,sha256=K4XY8bihmBMleRTZ2viMPiD2Y2HWxFnlgIJi6dd_PYE,6588
|
95
94
|
mcp_agent/mcp/sampling.py,sha256=vzWrIdI1CyFSxDWO-O69TpD6RwQcCM694BqMlYPVtaw,4584
|
96
95
|
mcp_agent/mcp/helpers/__init__.py,sha256=sKqwlUR3jSsd9PVJKjXtxHgZA1YOdzPtsSW4xVey77Q,52
|
@@ -99,19 +98,19 @@ mcp_agent/mcp/prompts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
|
|
99
98
|
mcp_agent/mcp/prompts/__main__.py,sha256=gr1Tdz9fcK0EXjEuZg_BOnKUmvhYq5AH2lFZicVyNb0,237
|
100
99
|
mcp_agent/mcp/prompts/prompt_constants.py,sha256=Q9W0t3rOXl2LHIG9wcghApUV2QZ1iICuo7SwVwHUf3c,566
|
101
100
|
mcp_agent/mcp/prompts/prompt_helpers.py,sha256=Joqo2t09pTKDP-Wge3G-ozPEHikzjaqwV6GVk8hNR50,7534
|
102
|
-
mcp_agent/mcp/prompts/prompt_load.py,sha256=
|
101
|
+
mcp_agent/mcp/prompts/prompt_load.py,sha256=VkcY6dD1jRCu-OB5AtSO8YwVATjEoYCkyAIGIujIHmw,5583
|
103
102
|
mcp_agent/mcp/prompts/prompt_server.py,sha256=tXtQd4EnH86MmdAvHlXm4oOS1dWLSCW5PvoA7uU1TvA,16493
|
104
103
|
mcp_agent/mcp/prompts/prompt_template.py,sha256=EejiqGkau8OizORNyKTUwUjrPof5V-hH1H_MBQoQfXw,15732
|
105
104
|
mcp_agent/mcp_server/__init__.py,sha256=zBU51ITHIEPScd9nRafnhEddsWqXRPAAvHhkrbRI2_4,155
|
106
|
-
mcp_agent/mcp_server/agent_server.py,sha256=
|
105
|
+
mcp_agent/mcp_server/agent_server.py,sha256=5jEHJcoKHKRhMAwn3HArqiqtiiLcFZvantABoEHSw8k,6470
|
107
106
|
mcp_agent/resources/examples/data-analysis/analysis-campaign.py,sha256=QdNdo0-7LR4Uzw61hEU_jVKmWyk6A9YpGo81kMwVobM,7267
|
108
107
|
mcp_agent/resources/examples/data-analysis/analysis.py,sha256=M9z8Q4YC5OGuqSa5uefYmmfmctqMn-WqCSfg5LI407o,2609
|
109
108
|
mcp_agent/resources/examples/data-analysis/fastagent.config.yaml,sha256=ini94PHyJCfgpjcjHKMMbGuHs6LIj46F1NwY0ll5HVk,1609
|
110
109
|
mcp_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv,sha256=pcMeOL1_r8m8MziE6xgbBrQbjl5Ijo98yycZn7O-dlk,227977
|
111
110
|
mcp_agent/resources/examples/in_dev/agent_build.py,sha256=eetMEdYDbmnRH4CLO7chpQucAar3OE7iVzD_pnMjIGs,2854
|
112
111
|
mcp_agent/resources/examples/in_dev/slides.py,sha256=-SEFeGIg9SLF253NIxmA0NjlanLe8CR1yjDBBp2LXgs,4904
|
113
|
-
mcp_agent/resources/examples/internal/agent.py,sha256=
|
114
|
-
mcp_agent/resources/examples/internal/fastagent.config.yaml,sha256=
|
112
|
+
mcp_agent/resources/examples/internal/agent.py,sha256=JDfb-64gKoEG6ihsyyVWkuY9XNOdC1-P3r9qwG_qN28,497
|
113
|
+
mcp_agent/resources/examples/internal/fastagent.config.yaml,sha256=EPJW8vsRfYq57w2U2gPF0rTAxGhkaLgiYXw0mnpXsdM,2060
|
115
114
|
mcp_agent/resources/examples/internal/history_transfer.py,sha256=ETyX2wMMvUnMpUhu4ij9ZTpw2wQR6vPpBwlXJswPnhM,1157
|
116
115
|
mcp_agent/resources/examples/internal/job.py,sha256=ANF3c01gHJ4O4pIxaAtC3rdgYqVObMySaCUBS4dApW4,4102
|
117
116
|
mcp_agent/resources/examples/internal/prompt_category.py,sha256=kMvqNX_zu0sV-kTaAR3skc_tsq9t8QSEofciK0m4aJc,551
|
@@ -135,8 +134,8 @@ mcp_agent/resources/examples/workflows/orchestrator.py,sha256=rOGilFTliWWnZ3Jx5w
|
|
135
134
|
mcp_agent/resources/examples/workflows/parallel.py,sha256=n0dFN26QvYd2wjgohcaUBflac2SzXYx-bCyxMSousJE,1884
|
136
135
|
mcp_agent/resources/examples/workflows/router.py,sha256=E4x_-c3l4YW9w1i4ARcDtkdeqIdbWEGfsMzwLYpdbVc,1677
|
137
136
|
mcp_agent/ui/console_display.py,sha256=TVGDtJ37hc6UG0ei9g7ZPZZfFNeS1MYozt-Mx8HsPCk,9752
|
138
|
-
fast_agent_mcp-0.2.
|
139
|
-
fast_agent_mcp-0.2.
|
140
|
-
fast_agent_mcp-0.2.
|
141
|
-
fast_agent_mcp-0.2.
|
142
|
-
fast_agent_mcp-0.2.
|
137
|
+
fast_agent_mcp-0.2.5.dist-info/METADATA,sha256=oL4nXJVnNJOv5M5KccLqjAY7jZsygvnIY2-1ZdXb1SI,29839
|
138
|
+
fast_agent_mcp-0.2.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
139
|
+
fast_agent_mcp-0.2.5.dist-info/entry_points.txt,sha256=qPM7vwtN1_KmP3dXehxgiCxUBHtqP7yfenZigztvY-w,226
|
140
|
+
fast_agent_mcp-0.2.5.dist-info/licenses/LICENSE,sha256=cN3FxDURL9XuzE5mhK9L2paZo82LTfjwCYVT7e3j0e4,10939
|
141
|
+
fast_agent_mcp-0.2.5.dist-info/RECORD,,
|
mcp_agent/agents/base_agent.py
CHANGED
@@ -120,28 +120,30 @@ class BaseAgent(MCPAggregator, AgentProtocol):
|
|
120
120
|
llm_factory: Union[Type[AugmentedLLMProtocol], Callable[..., AugmentedLLMProtocol]],
|
121
121
|
model: Optional[str] = None,
|
122
122
|
request_params: Optional[RequestParams] = None,
|
123
|
-
**additional_kwargs
|
123
|
+
**additional_kwargs,
|
124
124
|
) -> AugmentedLLMProtocol:
|
125
125
|
"""
|
126
126
|
Create and attach an LLM instance to this agent.
|
127
|
-
|
127
|
+
|
128
128
|
Parameters have the following precedence (highest to lowest):
|
129
129
|
1. Explicitly passed parameters to this method
|
130
130
|
2. Agent's default_request_params
|
131
131
|
3. LLM's default values
|
132
|
-
|
132
|
+
|
133
133
|
Args:
|
134
134
|
llm_factory: A class or callable that constructs an AugmentedLLM
|
135
135
|
model: Optional model name override
|
136
136
|
request_params: Optional request parameters override
|
137
137
|
**additional_kwargs: Additional parameters passed to the LLM constructor
|
138
|
-
|
138
|
+
|
139
139
|
Returns:
|
140
140
|
The created LLM instance
|
141
141
|
"""
|
142
142
|
# Start with agent's default params
|
143
|
-
effective_params =
|
144
|
-
|
143
|
+
effective_params = (
|
144
|
+
self._default_request_params.model_copy() if self._default_request_params else None
|
145
|
+
)
|
146
|
+
|
145
147
|
# Override with explicitly passed request_params
|
146
148
|
if request_params:
|
147
149
|
if effective_params:
|
@@ -151,18 +153,16 @@ class BaseAgent(MCPAggregator, AgentProtocol):
|
|
151
153
|
setattr(effective_params, k, v)
|
152
154
|
else:
|
153
155
|
effective_params = request_params
|
154
|
-
|
156
|
+
|
155
157
|
# Override model if explicitly specified
|
156
158
|
if model and effective_params:
|
157
159
|
effective_params.model = model
|
158
|
-
|
160
|
+
|
159
161
|
# Create the LLM instance
|
160
162
|
self._llm = llm_factory(
|
161
|
-
agent=self,
|
162
|
-
request_params=effective_params,
|
163
|
-
**additional_kwargs
|
163
|
+
agent=self, request_params=effective_params, context=self._context, **additional_kwargs
|
164
164
|
)
|
165
|
-
|
165
|
+
|
166
166
|
return self._llm
|
167
167
|
|
168
168
|
async def shutdown(self) -> None:
|
@@ -468,7 +468,7 @@ class BaseAgent(MCPAggregator, AgentProtocol):
|
|
468
468
|
|
469
469
|
# Get the prompt - this will search all servers if needed
|
470
470
|
self.logger.debug(f"Loading prompt '{prompt_name}'")
|
471
|
-
prompt_result = await self.get_prompt(prompt_name, arguments, server_name)
|
471
|
+
prompt_result: GetPromptResult = await self.get_prompt(prompt_name, arguments, server_name)
|
472
472
|
|
473
473
|
if not prompt_result or not prompt_result.messages:
|
474
474
|
error_msg = f"Prompt '{prompt_name}' could not be found or contains no messages"
|
mcp_agent/cli/main.py
CHANGED
@@ -62,6 +62,7 @@ def main(
|
|
62
62
|
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose mode"),
|
63
63
|
quiet: bool = typer.Option(False, "--quiet", "-q", help="Disable output"),
|
64
64
|
color: bool = typer.Option(True, "--color/--no-color", help="Enable/disable color output"),
|
65
|
+
version: bool = typer.Option(False, "--version", help="Show version and exit"),
|
65
66
|
) -> None:
|
66
67
|
"""FastAgent CLI - Build effective agents using Model Context Protocol (MCP).
|
67
68
|
|
@@ -70,6 +71,16 @@ def main(
|
|
70
71
|
application.verbosity = 1 if verbose else 0 if not quiet else -1
|
71
72
|
application.console = application.console if color else None
|
72
73
|
|
74
|
+
# Handle version flag
|
75
|
+
if version:
|
76
|
+
from importlib.metadata import version as get_version
|
77
|
+
try:
|
78
|
+
app_version = get_version("fast-agent-mcp")
|
79
|
+
except: # noqa: E722
|
80
|
+
app_version = "unknown"
|
81
|
+
console.print(f"fast-agent-mcp v{app_version}")
|
82
|
+
raise typer.Exit()
|
83
|
+
|
73
84
|
# Show welcome message if no command was invoked
|
74
85
|
if ctx.invoked_subcommand is None:
|
75
86
|
show_welcome()
|
mcp_agent/config.py
CHANGED
@@ -40,7 +40,7 @@ class MCPRootSettings(BaseModel):
|
|
40
40
|
@classmethod
|
41
41
|
def validate_uri(cls, v: str) -> str:
|
42
42
|
"""Validate that the URI starts with file:// (required by specification 2024-11-05)"""
|
43
|
-
if not v.startswith("file://"):
|
43
|
+
if v and not v.startswith("file://"):
|
44
44
|
raise ValueError("Root URI must start with file://")
|
45
45
|
return v
|
46
46
|
|
@@ -276,9 +276,9 @@ class Settings(BaseSettings):
|
|
276
276
|
# Check current directory and parent directories
|
277
277
|
while current_dir != current_dir.parent:
|
278
278
|
for filename in [
|
279
|
+
"fastagent.config.yaml",
|
279
280
|
"mcp-agent.config.yaml",
|
280
281
|
"mcp_agent.config.yaml",
|
281
|
-
"fastagent.config.yaml",
|
282
282
|
]:
|
283
283
|
config_path = current_dir / filename
|
284
284
|
if config_path.exists():
|
@@ -306,15 +306,33 @@ def get_settings(config_path: str | None = None) -> Settings:
|
|
306
306
|
return merged
|
307
307
|
|
308
308
|
global _settings
|
309
|
-
|
309
|
+
|
310
|
+
# If we have a specific config path, always reload settings
|
311
|
+
# This ensures each test gets its own config
|
312
|
+
if config_path:
|
313
|
+
# Reset for the new path
|
314
|
+
_settings = None
|
315
|
+
elif _settings:
|
316
|
+
# Use cached settings only for no specific path
|
310
317
|
return _settings
|
311
318
|
|
312
|
-
|
319
|
+
# Handle config path - convert string to Path if needed
|
320
|
+
if config_path:
|
321
|
+
config_file = Path(config_path)
|
322
|
+
# If it's a relative path and doesn't exist, try finding it
|
323
|
+
if not config_file.is_absolute() and not config_file.exists():
|
324
|
+
# Try resolving against current directory first
|
325
|
+
resolved_path = Path.cwd() / config_file.name
|
326
|
+
if resolved_path.exists():
|
327
|
+
config_file = resolved_path
|
328
|
+
else:
|
329
|
+
config_file = Settings.find_config()
|
330
|
+
|
313
331
|
merged_settings = {}
|
314
332
|
|
315
333
|
if config_file:
|
316
334
|
if not config_file.exists():
|
317
|
-
|
335
|
+
print(f"Warning: Specified config file does not exist: {config_file}")
|
318
336
|
else:
|
319
337
|
import yaml # pylint: disable=C0415
|
320
338
|
|
@@ -326,11 +344,14 @@ def get_settings(config_path: str | None = None) -> Settings:
|
|
326
344
|
# but stop after finding the first one
|
327
345
|
current_dir = config_file.parent
|
328
346
|
found_secrets = False
|
347
|
+
# Start with the absolute path of the config file's directory
|
348
|
+
current_dir = config_file.parent.resolve()
|
349
|
+
|
329
350
|
while current_dir != current_dir.parent and not found_secrets:
|
330
351
|
for secrets_filename in [
|
352
|
+
"fastagent.secrets.yaml",
|
331
353
|
"mcp-agent.secrets.yaml",
|
332
354
|
"mcp_agent.secrets.yaml",
|
333
|
-
"fastagent.secrets.yaml",
|
334
355
|
]:
|
335
356
|
secrets_file = current_dir / secrets_filename
|
336
357
|
if secrets_file.exists():
|
@@ -340,7 +361,8 @@ def get_settings(config_path: str | None = None) -> Settings:
|
|
340
361
|
found_secrets = True
|
341
362
|
break
|
342
363
|
if not found_secrets:
|
343
|
-
|
364
|
+
# Get the absolute path of the parent directory
|
365
|
+
current_dir = current_dir.parent.resolve()
|
344
366
|
|
345
367
|
_settings = Settings(**merged_settings)
|
346
368
|
return _settings
|
mcp_agent/context.py
CHANGED
@@ -234,5 +234,7 @@ def get_current_context() -> Context:
|
|
234
234
|
def get_current_config():
|
235
235
|
"""
|
236
236
|
Get the current application config.
|
237
|
+
|
238
|
+
Returns the context config if available, otherwise falls back to global settings.
|
237
239
|
"""
|
238
240
|
return get_current_context().config or get_settings()
|
mcp_agent/core/direct_factory.py
CHANGED
@@ -148,10 +148,7 @@ async def create_agents_by_type(
|
|
148
148
|
|
149
149
|
# Attach LLM to the agent
|
150
150
|
llm_factory = model_factory_func(model=config.model)
|
151
|
-
await agent.attach_llm(
|
152
|
-
llm_factory,
|
153
|
-
request_params=config.default_request_params
|
154
|
-
)
|
151
|
+
await agent.attach_llm(llm_factory, request_params=config.default_request_params)
|
155
152
|
result_agents[name] = agent
|
156
153
|
|
157
154
|
elif agent_type == AgentType.ORCHESTRATOR:
|
@@ -185,8 +182,7 @@ async def create_agents_by_type(
|
|
185
182
|
# Attach LLM to the orchestrator
|
186
183
|
llm_factory = model_factory_func(model=config.model)
|
187
184
|
await orchestrator.attach_llm(
|
188
|
-
llm_factory,
|
189
|
-
request_params=config.default_request_params
|
185
|
+
llm_factory, request_params=config.default_request_params
|
190
186
|
)
|
191
187
|
|
192
188
|
result_agents[name] = orchestrator
|
@@ -201,9 +197,7 @@ async def create_agents_by_type(
|
|
201
197
|
# Create default fan-in agent with auto-generated name
|
202
198
|
fan_in_name = f"{name}_fan_in"
|
203
199
|
fan_in_agent = await _create_default_fan_in_agent(
|
204
|
-
fan_in_name,
|
205
|
-
app_instance.context,
|
206
|
-
model_factory_func
|
200
|
+
fan_in_name, app_instance.context, model_factory_func
|
207
201
|
)
|
208
202
|
# Add to result_agents so it's registered properly
|
209
203
|
result_agents[fan_in_name] = fan_in_agent
|
@@ -248,10 +242,7 @@ async def create_agents_by_type(
|
|
248
242
|
|
249
243
|
# Attach LLM to the router
|
250
244
|
llm_factory = model_factory_func(model=config.model)
|
251
|
-
await router.attach_llm(
|
252
|
-
llm_factory,
|
253
|
-
request_params=config.default_request_params
|
254
|
-
)
|
245
|
+
await router.attach_llm(llm_factory, request_params=config.default_request_params)
|
255
246
|
result_agents[name] = router
|
256
247
|
|
257
248
|
elif agent_type == AgentType.CHAIN:
|
@@ -459,7 +450,7 @@ async def _create_default_fan_in_agent(
|
|
459
450
|
default_config = AgentConfig(
|
460
451
|
name=fan_in_name,
|
461
452
|
model="passthrough",
|
462
|
-
instruction="You are a passthrough agent that combines outputs from parallel agents."
|
453
|
+
instruction="You are a passthrough agent that combines outputs from parallel agents.",
|
463
454
|
)
|
464
455
|
|
465
456
|
# Create and initialize the default agent
|
@@ -291,7 +291,7 @@ async def get_enhanced_input(
|
|
291
291
|
return f"SELECT_PROMPT:{cmd_parts[1].strip()}"
|
292
292
|
elif cmd == "exit":
|
293
293
|
return "EXIT"
|
294
|
-
elif cmd == "stop":
|
294
|
+
elif cmd.lower() == "stop":
|
295
295
|
return "STOP"
|
296
296
|
|
297
297
|
# Agent switching
|
@@ -420,7 +420,7 @@ async def get_argument_input(
|
|
420
420
|
prompt_session.app.exit()
|
421
421
|
|
422
422
|
|
423
|
-
async def handle_special_commands(command, agent_app=None):
|
423
|
+
async def handle_special_commands(command: str, agent_app=None):
|
424
424
|
"""Handle special input commands."""
|
425
425
|
# Quick guard for empty or None commands
|
426
426
|
if not command:
|
@@ -450,7 +450,7 @@ async def handle_special_commands(command, agent_app=None):
|
|
450
450
|
print("\033c", end="")
|
451
451
|
return True
|
452
452
|
|
453
|
-
elif command == "EXIT":
|
453
|
+
elif command.upper() == "EXIT":
|
454
454
|
raise PromptExitError("User requested to exit fast-agent session")
|
455
455
|
|
456
456
|
elif command == "LIST_AGENTS":
|
mcp_agent/core/fastagent.py
CHANGED
@@ -6,14 +6,15 @@ directly creates Agent instances without proxies.
|
|
6
6
|
|
7
7
|
import argparse
|
8
8
|
import asyncio
|
9
|
-
import
|
9
|
+
import sys
|
10
10
|
from contextlib import asynccontextmanager
|
11
|
+
from importlib.metadata import version as get_version
|
11
12
|
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, TypeVar
|
12
13
|
|
13
14
|
import yaml
|
14
15
|
|
16
|
+
from mcp_agent import config
|
15
17
|
from mcp_agent.app import MCPApp
|
16
|
-
from mcp_agent.config import Settings
|
17
18
|
from mcp_agent.context import Context
|
18
19
|
from mcp_agent.core.agent_app import AgentApp
|
19
20
|
from mcp_agent.core.direct_decorators import (
|
@@ -70,7 +71,7 @@ class FastAgent:
|
|
70
71
|
def __init__(
|
71
72
|
self,
|
72
73
|
name: str,
|
73
|
-
config_path:
|
74
|
+
config_path: str | None = None,
|
74
75
|
ignore_unknown_args: bool = False,
|
75
76
|
) -> None:
|
76
77
|
"""
|
@@ -101,6 +102,33 @@ class FastAgent:
|
|
101
102
|
action="store_true",
|
102
103
|
help="Disable progress display, tool and message logging for cleaner output",
|
103
104
|
)
|
105
|
+
parser.add_argument(
|
106
|
+
"--version",
|
107
|
+
action="store_true",
|
108
|
+
help="Show version and exit",
|
109
|
+
)
|
110
|
+
parser.add_argument(
|
111
|
+
"--server",
|
112
|
+
action="store_true",
|
113
|
+
help="Run as an MCP server",
|
114
|
+
)
|
115
|
+
parser.add_argument(
|
116
|
+
"--transport",
|
117
|
+
choices=["sse", "stdio"],
|
118
|
+
default="sse",
|
119
|
+
help="Transport protocol to use when running as a server (sse or stdio)",
|
120
|
+
)
|
121
|
+
parser.add_argument(
|
122
|
+
"--port",
|
123
|
+
type=int,
|
124
|
+
default=8000,
|
125
|
+
help="Port to use when running as a server with SSE transport",
|
126
|
+
)
|
127
|
+
parser.add_argument(
|
128
|
+
"--host",
|
129
|
+
default="0.0.0.0",
|
130
|
+
help="Host address to bind to when running as a server with SSE transport",
|
131
|
+
)
|
104
132
|
|
105
133
|
if ignore_unknown_args:
|
106
134
|
known_args, _ = parser.parse_known_args()
|
@@ -108,10 +136,28 @@ class FastAgent:
|
|
108
136
|
else:
|
109
137
|
self.args = parser.parse_args()
|
110
138
|
|
139
|
+
# Handle version flag
|
140
|
+
if self.args.version:
|
141
|
+
try:
|
142
|
+
app_version = get_version("fast-agent-mcp")
|
143
|
+
except: # noqa: E722
|
144
|
+
app_version = "unknown"
|
145
|
+
print(f"fast-agent-mcp v{app_version}")
|
146
|
+
sys.exit(0)
|
147
|
+
|
111
148
|
self.name = name
|
112
149
|
self.config_path = config_path
|
150
|
+
|
113
151
|
try:
|
152
|
+
# Load configuration directly for this instance
|
114
153
|
self._load_config()
|
154
|
+
|
155
|
+
# Create the app with our local settings
|
156
|
+
self.app = MCPApp(
|
157
|
+
name=name,
|
158
|
+
settings=config.Settings(**self.config) if hasattr(self, "config") else None,
|
159
|
+
)
|
160
|
+
|
115
161
|
except yaml.parser.ParserError as e:
|
116
162
|
handle_error(
|
117
163
|
e,
|
@@ -119,25 +165,30 @@ class FastAgent:
|
|
119
165
|
"There was an error parsing the config or secrets YAML configuration file.",
|
120
166
|
)
|
121
167
|
raise SystemExit(1)
|
122
|
-
# Create the MCPApp with the config
|
123
|
-
self.app = MCPApp(
|
124
|
-
name=name,
|
125
|
-
settings=Settings(**self.config) if hasattr(self, "config") else None,
|
126
|
-
)
|
127
168
|
|
128
169
|
# Dictionary to store agent configurations from decorators
|
129
170
|
self.agents: Dict[str, Dict[str, Any]] = {}
|
130
171
|
|
131
172
|
def _load_config(self) -> None:
|
132
|
-
"""Load configuration from YAML file
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
173
|
+
"""Load configuration from YAML file including secrets using get_settings
|
174
|
+
but without relying on the global cache."""
|
175
|
+
|
176
|
+
# Import but make a local copy to avoid affecting the global state
|
177
|
+
from mcp_agent.config import _settings, get_settings
|
178
|
+
|
179
|
+
# Temporarily clear the global settings to ensure a fresh load
|
180
|
+
old_settings = _settings
|
181
|
+
_settings = None
|
182
|
+
|
183
|
+
try:
|
184
|
+
# Use get_settings to load config - this handles all paths and secrets merging
|
185
|
+
settings = get_settings(self.config_path)
|
186
|
+
|
187
|
+
# Convert to dict for backward compatibility
|
188
|
+
self.config = settings.model_dump() if settings else {}
|
189
|
+
finally:
|
190
|
+
# Restore the original global settings
|
191
|
+
_settings = old_settings
|
141
192
|
|
142
193
|
@property
|
143
194
|
def context(self) -> Context:
|
@@ -166,16 +217,17 @@ class FastAgent:
|
|
166
217
|
quiet_mode = hasattr(self, "args") and self.args.quiet
|
167
218
|
|
168
219
|
try:
|
169
|
-
async with self.app.run()
|
220
|
+
async with self.app.run():
|
170
221
|
# Apply quiet mode if requested
|
171
222
|
if (
|
172
223
|
quiet_mode
|
173
|
-
and hasattr(
|
174
|
-
and hasattr(
|
224
|
+
and hasattr(self.app.context, "config")
|
225
|
+
and hasattr(self.app.context.config, "logger")
|
175
226
|
):
|
176
|
-
|
177
|
-
|
178
|
-
|
227
|
+
# Update our app's config directly
|
228
|
+
self.app.context.config.logger.progress_display = False
|
229
|
+
self.app.context.config.logger.show_chat = False
|
230
|
+
self.app.context.config.logger.show_tools = False
|
179
231
|
|
180
232
|
# Directly disable the progress display singleton
|
181
233
|
from mcp_agent.progress_display import progress_display
|
@@ -205,6 +257,41 @@ class FastAgent:
|
|
205
257
|
# Create a wrapper with all agents for simplified access
|
206
258
|
wrapper = AgentApp(active_agents)
|
207
259
|
|
260
|
+
# Handle command line options that should be processed after agent initialization
|
261
|
+
|
262
|
+
# Handle --server option
|
263
|
+
if hasattr(self, "args") and self.args.server:
|
264
|
+
try:
|
265
|
+
# Print info message if not in quiet mode
|
266
|
+
if not quiet_mode:
|
267
|
+
print(f"Starting FastAgent '{self.name}' in server mode")
|
268
|
+
print(f"Transport: {self.args.transport}")
|
269
|
+
if self.args.transport == "sse":
|
270
|
+
print(f"Listening on {self.args.host}:{self.args.port}")
|
271
|
+
print("Press Ctrl+C to stop")
|
272
|
+
|
273
|
+
# Create the MCP server
|
274
|
+
from mcp_agent.mcp_server import AgentMCPServer
|
275
|
+
|
276
|
+
mcp_server = AgentMCPServer(
|
277
|
+
agent_app=wrapper,
|
278
|
+
server_name=f"{self.name}-MCP-Server",
|
279
|
+
)
|
280
|
+
|
281
|
+
# Run the server directly (this is a blocking call)
|
282
|
+
await mcp_server.run_async(
|
283
|
+
transport=self.args.transport, host=self.args.host, port=self.args.port
|
284
|
+
)
|
285
|
+
except KeyboardInterrupt:
|
286
|
+
if not quiet_mode:
|
287
|
+
print("\nServer stopped by user (Ctrl+C)")
|
288
|
+
except Exception as e:
|
289
|
+
if not quiet_mode:
|
290
|
+
print(f"\nServer stopped with error: {e}")
|
291
|
+
|
292
|
+
# Exit after server shutdown
|
293
|
+
raise SystemExit(0)
|
294
|
+
|
208
295
|
# Handle direct message sending if --agent and --message are provided
|
209
296
|
if hasattr(self, "args") and self.args.agent and self.args.message:
|
210
297
|
agent_name = self.args.agent
|
@@ -222,7 +309,8 @@ class FastAgent:
|
|
222
309
|
agent = active_agents[agent_name]
|
223
310
|
response = await agent.send(message)
|
224
311
|
|
225
|
-
#
|
312
|
+
# In quiet mode, just print the raw response
|
313
|
+
# The chat display should already be turned off by the configuration
|
226
314
|
if self.args.quiet:
|
227
315
|
print(f"{response}")
|
228
316
|
|
@@ -313,6 +401,58 @@ class FastAgent:
|
|
313
401
|
else:
|
314
402
|
handle_error(e, error_type or "Error", "An unexpected error occurred.")
|
315
403
|
|
404
|
+
async def start_server(
|
405
|
+
self,
|
406
|
+
transport: str = "sse",
|
407
|
+
host: str = "0.0.0.0",
|
408
|
+
port: int = 8000,
|
409
|
+
server_name: Optional[str] = None,
|
410
|
+
server_description: Optional[str] = None,
|
411
|
+
) -> None:
|
412
|
+
"""
|
413
|
+
Start the application as an MCP server.
|
414
|
+
This method initializes agents and exposes them through an MCP server.
|
415
|
+
It is a blocking method that runs until the server is stopped.
|
416
|
+
|
417
|
+
Args:
|
418
|
+
transport: Transport protocol to use ("stdio" or "sse")
|
419
|
+
host: Host address for the server when using SSE
|
420
|
+
port: Port for the server when using SSE
|
421
|
+
server_name: Optional custom name for the MCP server
|
422
|
+
server_description: Optional description for the MCP server
|
423
|
+
"""
|
424
|
+
# This method simply updates the command line arguments and uses run()
|
425
|
+
# to ensure we follow the same initialization path for all operations
|
426
|
+
|
427
|
+
# Store original args
|
428
|
+
original_args = None
|
429
|
+
if hasattr(self, "args"):
|
430
|
+
original_args = self.args
|
431
|
+
|
432
|
+
# Create our own args object with server settings
|
433
|
+
from argparse import Namespace
|
434
|
+
|
435
|
+
self.args = Namespace()
|
436
|
+
self.args.server = True
|
437
|
+
self.args.transport = transport
|
438
|
+
self.args.host = host
|
439
|
+
self.args.port = port
|
440
|
+
self.args.quiet = (
|
441
|
+
original_args.quiet if original_args and hasattr(original_args, "quiet") else False
|
442
|
+
)
|
443
|
+
self.args.model = None
|
444
|
+
if hasattr(original_args, "model"):
|
445
|
+
self.args.model = original_args.model
|
446
|
+
|
447
|
+
# Run the application, which will detect the server flag and start server mode
|
448
|
+
async with self.run():
|
449
|
+
pass # This won't be reached due to SystemExit in run()
|
450
|
+
|
451
|
+
# Restore original args (if we get here)
|
452
|
+
if original_args:
|
453
|
+
self.args = original_args
|
454
|
+
|
455
|
+
# Keep run_with_mcp_server for backward compatibility
|
316
456
|
async def run_with_mcp_server(
|
317
457
|
self,
|
318
458
|
transport: str = "sse",
|
@@ -323,6 +463,8 @@ class FastAgent:
|
|
323
463
|
) -> None:
|
324
464
|
"""
|
325
465
|
Run the application and expose agents through an MCP server.
|
466
|
+
This method is kept for backward compatibility.
|
467
|
+
For new code, use start_server() instead.
|
326
468
|
|
327
469
|
Args:
|
328
470
|
transport: Transport protocol to use ("stdio" or "sse")
|
@@ -331,26 +473,40 @@ class FastAgent:
|
|
331
473
|
server_name: Optional custom name for the MCP server
|
332
474
|
server_description: Optional description for the MCP server
|
333
475
|
"""
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
server_description=server_description,
|
342
|
-
)
|
343
|
-
|
344
|
-
# Run the MCP server in a separate task
|
345
|
-
server_task = asyncio.create_task(
|
346
|
-
mcp_server.run_async(transport=transport, host=host, port=port)
|
347
|
-
)
|
476
|
+
await self.start_server(
|
477
|
+
transport=transport,
|
478
|
+
host=host,
|
479
|
+
port=port,
|
480
|
+
server_name=server_name,
|
481
|
+
server_description=server_description,
|
482
|
+
)
|
348
483
|
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
484
|
+
async def main(self):
|
485
|
+
"""
|
486
|
+
Helper method for checking if server mode was requested.
|
487
|
+
|
488
|
+
Usage:
|
489
|
+
```python
|
490
|
+
fast = FastAgent("My App")
|
491
|
+
|
492
|
+
@fast.agent(...)
|
493
|
+
async def app_main():
|
494
|
+
# Check if server mode was requested
|
495
|
+
# This doesn't actually do anything - the check happens in run()
|
496
|
+
# But it provides a way for application code to know if server mode
|
497
|
+
# was requested for conditionals
|
498
|
+
is_server_mode = hasattr(self, "args") and self.args.server
|
499
|
+
|
500
|
+
# Normal run - this will handle server mode automatically if requested
|
501
|
+
async with fast.run() as agent:
|
502
|
+
# This code only executes for normal mode
|
503
|
+
# Server mode will exit before reaching here
|
504
|
+
await agent.send("Hello")
|
505
|
+
```
|
506
|
+
|
507
|
+
Returns:
|
508
|
+
bool: True if --server flag is set, False otherwise
|
509
|
+
"""
|
510
|
+
# Just check if the flag is set, no action here
|
511
|
+
# The actual server code will be handled by run()
|
512
|
+
return hasattr(self, "args") and self.args.server
|
mcp_agent/llm/augmented_llm.py
CHANGED
@@ -39,7 +39,6 @@ from mcp_agent.mcp.interfaces import (
|
|
39
39
|
from mcp_agent.mcp.mcp_aggregator import MCPAggregator
|
40
40
|
from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
|
41
41
|
from mcp_agent.mcp.prompt_render import render_multipart_message
|
42
|
-
from mcp_agent.mcp.prompt_serialization import multipart_messages_to_delimited_format
|
43
42
|
from mcp_agent.ui.console_display import ConsoleDisplay
|
44
43
|
|
45
44
|
# Define type variables locally
|
@@ -435,16 +434,15 @@ class AugmentedLLM(ContextDependent, AugmentedLLMProtocol, Generic[MessageParamT
|
|
435
434
|
|
436
435
|
async def _save_history(self, filename: str) -> None:
|
437
436
|
"""
|
438
|
-
Save the Message History to a file in a
|
437
|
+
Save the Message History to a file in a format determined by the file extension.
|
438
|
+
|
439
|
+
Uses JSON format for .json files (MCP SDK compatible format) and
|
440
|
+
delimited text format for other extensions.
|
439
441
|
"""
|
440
|
-
|
441
|
-
delimited_content = multipart_messages_to_delimited_format(
|
442
|
-
self._message_history,
|
443
|
-
)
|
442
|
+
from mcp_agent.mcp.prompt_serialization import save_messages_to_file
|
444
443
|
|
445
|
-
#
|
446
|
-
|
447
|
-
f.write("\n\n".join(delimited_content))
|
444
|
+
# Save messages using the unified save function that auto-detects format
|
445
|
+
save_messages_to_file(self._message_history, filename)
|
448
446
|
|
449
447
|
@abstractmethod
|
450
448
|
async def _apply_prompt_provider_specific(
|
@@ -143,7 +143,9 @@ class PassthroughLLM(AugmentedLLM):
|
|
143
143
|
|
144
144
|
# TODO -- improve when we support Audio/Multimodal gen
|
145
145
|
if self.is_tool_call(last_message):
|
146
|
-
|
146
|
+
result = Prompt.assistant(await self.generate_str(last_message.first_text()))
|
147
|
+
await self.show_assistant_message(result.first_text())
|
148
|
+
return result
|
147
149
|
|
148
150
|
if last_message.first_text().startswith(FIXED_RESPONSE_INDICATOR):
|
149
151
|
self._fixed_response = (
|
mcp_agent/llm/model_factory.py
CHANGED
@@ -103,7 +103,7 @@ class ModelFactory:
|
|
103
103
|
"sonnet": "claude-3-7-sonnet-latest",
|
104
104
|
"sonnet35": "claude-3-5-sonnet-latest",
|
105
105
|
"sonnet37": "claude-3-7-sonnet-latest",
|
106
|
-
"claude": "claude-3-
|
106
|
+
"claude": "claude-3-7-sonnet-latest",
|
107
107
|
"haiku": "claude-3-5-haiku-latest",
|
108
108
|
"haiku3": "claude-3-haiku-20240307",
|
109
109
|
"haiku35": "claude-3-5-haiku-latest",
|
@@ -188,24 +188,22 @@ class ModelFactory:
|
|
188
188
|
|
189
189
|
# Create a factory function matching the updated attach_llm protocol
|
190
190
|
def factory(
|
191
|
-
agent: Agent,
|
192
|
-
request_params: Optional[RequestParams] = None,
|
193
|
-
**kwargs
|
191
|
+
agent: Agent, request_params: Optional[RequestParams] = None, **kwargs
|
194
192
|
) -> AugmentedLLMProtocol:
|
195
193
|
# Create base params with parsed model name
|
196
194
|
base_params = RequestParams()
|
197
195
|
base_params.model = config.model_name # Use the parsed model name, not the alias
|
198
|
-
|
196
|
+
|
199
197
|
# Add reasoning effort if available
|
200
198
|
if config.reasoning_effort:
|
201
199
|
kwargs["reasoning_effort"] = config.reasoning_effort.value
|
202
|
-
|
200
|
+
|
203
201
|
# Forward all arguments to LLM constructor
|
204
202
|
llm_args = {
|
205
203
|
"agent": agent,
|
206
204
|
"model": config.model_name,
|
207
205
|
"request_params": request_params,
|
208
|
-
**kwargs
|
206
|
+
**kwargs,
|
209
207
|
}
|
210
208
|
|
211
209
|
llm: AugmentedLLMProtocol = llm_class(**llm_args)
|
@@ -110,6 +110,48 @@ def load_messages_from_json_file(file_path: str) -> List[PromptMessageMultipart]
|
|
110
110
|
return json_to_multipart_messages(json_str)
|
111
111
|
|
112
112
|
|
113
|
+
def save_messages_to_file(messages: List[PromptMessageMultipart], file_path: str) -> None:
|
114
|
+
"""
|
115
|
+
Save PromptMessageMultipart objects to a file, with format determined by file extension.
|
116
|
+
|
117
|
+
Uses JSON format for .json files and delimited text format for other extensions.
|
118
|
+
|
119
|
+
Args:
|
120
|
+
messages: List of PromptMessageMultipart objects
|
121
|
+
file_path: Path to save the file
|
122
|
+
"""
|
123
|
+
path_str = str(file_path).lower()
|
124
|
+
|
125
|
+
if path_str.endswith(".json"):
|
126
|
+
# Use JSON format for .json files (MCP SDK compatible format)
|
127
|
+
save_messages_to_json_file(messages, file_path)
|
128
|
+
else:
|
129
|
+
# Use delimited text format for other extensions
|
130
|
+
save_messages_to_delimited_file(messages, file_path)
|
131
|
+
|
132
|
+
|
133
|
+
def load_messages_from_file(file_path: str) -> List[PromptMessageMultipart]:
|
134
|
+
"""
|
135
|
+
Load PromptMessageMultipart objects from a file, with format determined by file extension.
|
136
|
+
|
137
|
+
Uses JSON format for .json files and delimited text format for other extensions.
|
138
|
+
|
139
|
+
Args:
|
140
|
+
file_path: Path to the file
|
141
|
+
|
142
|
+
Returns:
|
143
|
+
List of PromptMessageMultipart objects
|
144
|
+
"""
|
145
|
+
path_str = str(file_path).lower()
|
146
|
+
|
147
|
+
if path_str.endswith(".json"):
|
148
|
+
# Use JSON format for .json files (MCP SDK compatible format)
|
149
|
+
return load_messages_from_json_file(file_path)
|
150
|
+
else:
|
151
|
+
# Use delimited text format for other extensions
|
152
|
+
return load_messages_from_delimited_file(file_path)
|
153
|
+
|
154
|
+
|
113
155
|
# -------------------------------------------------------------------------
|
114
156
|
# Delimited Text Format Functions
|
115
157
|
# -------------------------------------------------------------------------
|
@@ -101,9 +101,57 @@ def create_resource_message(
|
|
101
101
|
|
102
102
|
|
103
103
|
def load_prompt(file: Path) -> List[PromptMessage]:
|
104
|
-
|
105
|
-
return
|
104
|
+
"""
|
105
|
+
Load a prompt from a file and return as PromptMessage objects.
|
106
|
+
|
107
|
+
The loader uses file extension to determine the format:
|
108
|
+
- .json files are loaded as MCP SDK compatible JSON format
|
109
|
+
- All other files are loaded using the template-based delimited format
|
110
|
+
|
111
|
+
Args:
|
112
|
+
file: Path to the prompt file
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
List of PromptMessage objects
|
116
|
+
"""
|
117
|
+
file_str = str(file).lower()
|
118
|
+
|
119
|
+
if file_str.endswith(".json"):
|
120
|
+
# JSON format (MCP SDK compatible)
|
121
|
+
from mcp_agent.mcp.prompt_serialization import load_messages_from_json_file
|
122
|
+
|
123
|
+
# Load multipart messages and convert to flat messages
|
124
|
+
multipart_messages = load_messages_from_json_file(str(file))
|
125
|
+
messages = []
|
126
|
+
for mp in multipart_messages:
|
127
|
+
messages.extend(mp.from_multipart())
|
128
|
+
return messages
|
129
|
+
else:
|
130
|
+
# Template-based format (delimited text)
|
131
|
+
template: PromptTemplate = PromptTemplateLoader().load_from_file(file)
|
132
|
+
return create_messages_with_resources(template.content_sections, [file])
|
106
133
|
|
107
134
|
|
108
135
|
def load_prompt_multipart(file: Path) -> List[PromptMessageMultipart]:
|
109
|
-
|
136
|
+
"""
|
137
|
+
Load a prompt from a file and return as PromptMessageMultipart objects.
|
138
|
+
|
139
|
+
The loader uses file extension to determine the format:
|
140
|
+
- .json files are loaded as MCP SDK compatible JSON format
|
141
|
+
- All other files are loaded using the template-based delimited format
|
142
|
+
|
143
|
+
Args:
|
144
|
+
file: Path to the prompt file
|
145
|
+
|
146
|
+
Returns:
|
147
|
+
List of PromptMessageMultipart objects
|
148
|
+
"""
|
149
|
+
file_str = str(file).lower()
|
150
|
+
|
151
|
+
if file_str.endswith(".json"):
|
152
|
+
# JSON format (MCP SDK compatible)
|
153
|
+
from mcp_agent.mcp.prompt_serialization import load_messages_from_json_file
|
154
|
+
return load_messages_from_json_file(str(file))
|
155
|
+
else:
|
156
|
+
# Template-based format (delimited text)
|
157
|
+
return PromptMessageMultipart.to_multipart(load_prompt(file))
|
@@ -1,9 +1,13 @@
|
|
1
1
|
# src/mcp_agent/mcp_server/agent_server.py
|
2
2
|
|
3
|
+
import asyncio
|
4
|
+
|
3
5
|
from mcp.server.fastmcp import Context as MCPContext
|
4
6
|
from mcp.server.fastmcp import FastMCP
|
5
7
|
|
6
|
-
|
8
|
+
import mcp_agent
|
9
|
+
import mcp_agent.core
|
10
|
+
import mcp_agent.core.prompt
|
7
11
|
from mcp_agent.core.agent_app import AgentApp
|
8
12
|
|
9
13
|
|
@@ -14,7 +18,7 @@ class AgentMCPServer:
|
|
14
18
|
self,
|
15
19
|
agent_app: AgentApp,
|
16
20
|
server_name: str = "FastAgent-MCP-Server",
|
17
|
-
server_description: str = None,
|
21
|
+
server_description: str | None = None,
|
18
22
|
) -> None:
|
19
23
|
self.agent_app = agent_app
|
20
24
|
self.mcp_server = FastMCP(
|
@@ -26,10 +30,10 @@ class AgentMCPServer:
|
|
26
30
|
|
27
31
|
def setup_tools(self) -> None:
|
28
32
|
"""Register all agents as MCP tools."""
|
29
|
-
for agent_name,
|
30
|
-
self.register_agent_tools(agent_name,
|
33
|
+
for agent_name, agent in self.agent_app._agents.items():
|
34
|
+
self.register_agent_tools(agent_name, agent)
|
31
35
|
|
32
|
-
def register_agent_tools(self, agent_name: str,
|
36
|
+
def register_agent_tools(self, agent_name: str, agent) -> None:
|
33
37
|
"""Register tools for a specific agent."""
|
34
38
|
|
35
39
|
# Basic send message tool
|
@@ -41,13 +45,11 @@ class AgentMCPServer:
|
|
41
45
|
"""Send a message to the agent and return its response."""
|
42
46
|
|
43
47
|
# Get the agent's context
|
44
|
-
agent_context = None
|
45
|
-
if hasattr(agent_proxy, "_agent") and hasattr(agent_proxy._agent, "context"):
|
46
|
-
agent_context = agent_proxy._agent.context
|
48
|
+
agent_context = getattr(agent, "context", None)
|
47
49
|
|
48
50
|
# Define the function to execute
|
49
51
|
async def execute_send():
|
50
|
-
return await
|
52
|
+
return await agent.send(message)
|
51
53
|
|
52
54
|
# Execute with bridged context
|
53
55
|
if agent_context and ctx:
|
@@ -55,6 +57,25 @@ class AgentMCPServer:
|
|
55
57
|
else:
|
56
58
|
return await execute_send()
|
57
59
|
|
60
|
+
# Register a history prompt for this agent
|
61
|
+
@self.mcp_server.prompt(name=f"{agent_name}.history", description=f"Conversation history for the {agent_name} agent")
|
62
|
+
async def get_history_prompt() -> list:
|
63
|
+
"""Return the conversation history as MCP messages."""
|
64
|
+
# Get the conversation history from the agent's LLM
|
65
|
+
if not hasattr(agent, "_llm") or agent._llm is None:
|
66
|
+
return []
|
67
|
+
|
68
|
+
# Convert the multipart message history to standard PromptMessages
|
69
|
+
multipart_history = agent._llm.message_history
|
70
|
+
prompt_messages = mcp_agent.core.prompt.Prompt.from_multipart(multipart_history)
|
71
|
+
|
72
|
+
# In FastMCP, we need to return the raw list of messages
|
73
|
+
# that matches the structure that FastMCP expects (list of dicts with role/content)
|
74
|
+
return [
|
75
|
+
{"role": msg.role, "content": msg.content}
|
76
|
+
for msg in prompt_messages
|
77
|
+
]
|
78
|
+
|
58
79
|
def run(self, transport: str = "sse", host: str = "0.0.0.0", port: int = 8000) -> None:
|
59
80
|
"""Run the MCP server."""
|
60
81
|
if transport == "sse":
|
@@ -71,9 +92,19 @@ class AgentMCPServer:
|
|
71
92
|
if transport == "sse":
|
72
93
|
self.mcp_server.settings.host = host
|
73
94
|
self.mcp_server.settings.port = port
|
74
|
-
|
95
|
+
try:
|
96
|
+
await self.mcp_server.run_sse_async()
|
97
|
+
except (asyncio.CancelledError, KeyboardInterrupt):
|
98
|
+
# Gracefully handle cancellation during shutdown
|
99
|
+
await self.shutdown()
|
100
|
+
pass
|
75
101
|
else: # stdio
|
76
|
-
|
102
|
+
try:
|
103
|
+
await self.mcp_server.run_stdio_async()
|
104
|
+
except (asyncio.CancelledError, KeyboardInterrupt):
|
105
|
+
# Gracefully handle cancellation during shutdown
|
106
|
+
await self.shutdown()
|
107
|
+
pass
|
77
108
|
|
78
109
|
async def with_bridged_context(self, agent_context, mcp_context, func, *args, **kwargs):
|
79
110
|
"""
|
@@ -115,3 +146,21 @@ class AgentMCPServer:
|
|
115
146
|
# Remove MCP context reference
|
116
147
|
if hasattr(agent_context, "mcp_context"):
|
117
148
|
delattr(agent_context, "mcp_context")
|
149
|
+
|
150
|
+
async def shutdown(self):
|
151
|
+
"""Gracefully shutdown the MCP server and its resources."""
|
152
|
+
# Your MCP server may have additional cleanup code here
|
153
|
+
try:
|
154
|
+
# If your MCP server has a shutdown method, call it
|
155
|
+
if hasattr(self.mcp_server, "shutdown"):
|
156
|
+
await self.mcp_server.shutdown()
|
157
|
+
|
158
|
+
# Clean up any other resources
|
159
|
+
import asyncio
|
160
|
+
|
161
|
+
# Allow any pending tasks to clean up
|
162
|
+
await asyncio.sleep(0.5)
|
163
|
+
except Exception as e:
|
164
|
+
# Just log exceptions during shutdown, don't raise
|
165
|
+
print(f"Error during MCP server shutdown: {e}")
|
166
|
+
pass
|
@@ -7,13 +7,13 @@ fast = FastAgent("FastAgent Example")
|
|
7
7
|
|
8
8
|
|
9
9
|
# Define the agent
|
10
|
-
@fast.agent(servers=["category", "mcp_hfspace"])
|
10
|
+
@fast.agent(servers=["category", "mcp_hfspace","mcp_webcam"])
|
11
11
|
#@fast.agent(name="test")
|
12
12
|
async def main() -> None:
|
13
13
|
# use the --model command line switch or agent arguments to change model
|
14
14
|
async with fast.run() as agent:
|
15
15
|
# await agent.prompt(agent_name="test")
|
16
|
-
await agent
|
16
|
+
await agent()
|
17
17
|
|
18
18
|
|
19
19
|
if __name__ == "__main__":
|
@@ -1,56 +0,0 @@
|
|
1
|
-
import asyncio
|
2
|
-
|
3
|
-
from mcp.server import NotificationOptions
|
4
|
-
from mcp.server.fastmcp import FastMCP
|
5
|
-
from mcp.server.stdio import stdio_server
|
6
|
-
|
7
|
-
from mcp_agent.executor.temporal import get_temporal_client
|
8
|
-
from mcp_agent.telemetry.tracing import setup_tracing
|
9
|
-
|
10
|
-
app = FastMCP("mcp-agent-server")
|
11
|
-
|
12
|
-
setup_tracing("mcp-agent-server")
|
13
|
-
|
14
|
-
|
15
|
-
async def run() -> None:
|
16
|
-
async with stdio_server() as (read_stream, write_stream):
|
17
|
-
await app._mcp_server.run(
|
18
|
-
read_stream,
|
19
|
-
write_stream,
|
20
|
-
app._mcp_server.create_initialization_options(
|
21
|
-
notification_options=NotificationOptions(tools_changed=True, resources_changed=True)
|
22
|
-
),
|
23
|
-
)
|
24
|
-
|
25
|
-
|
26
|
-
@app.tool
|
27
|
-
async def run_workflow(query: str) -> None:
|
28
|
-
"""Run the workflow given its name or id"""
|
29
|
-
pass
|
30
|
-
|
31
|
-
|
32
|
-
@app.tool
|
33
|
-
async def pause_workflow(workflow_id: str) -> None:
|
34
|
-
"""Pause a running workflow."""
|
35
|
-
temporal_client = await get_temporal_client()
|
36
|
-
handle = temporal_client.get_workflow_handle(workflow_id)
|
37
|
-
await handle.signal("pause")
|
38
|
-
|
39
|
-
|
40
|
-
@app.tool
|
41
|
-
async def resume_workflow(workflow_id: str) -> None:
|
42
|
-
"""Resume a paused workflow."""
|
43
|
-
temporal_client = await get_temporal_client()
|
44
|
-
handle = temporal_client.get_workflow_handle(workflow_id)
|
45
|
-
await handle.signal("resume")
|
46
|
-
|
47
|
-
|
48
|
-
async def provide_user_input(workflow_id: str, input_data: str) -> None:
|
49
|
-
"""Provide user/human input to a waiting workflow step."""
|
50
|
-
temporal_client = await get_temporal_client()
|
51
|
-
handle = temporal_client.get_workflow_handle(workflow_id)
|
52
|
-
await handle.signal("human_input", input_data)
|
53
|
-
|
54
|
-
|
55
|
-
if __name__ == "__main__":
|
56
|
-
asyncio.run(run())
|
File without changes
|
File without changes
|
File without changes
|