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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fast-agent-mcp
3
- Version: 0.2.4
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://lmai.link/discord/mcp-agent"><img src="https://shields.io/discord/1089284610329952357" alt="discord" /></a>
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=lI4B9VhF0qplez20cPeegxPA1ZcFBIxzepm_FOhW_08,10758
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=NHcfnp5v-dCSxRvzEbavVaI7WOv5ir64IAFbGSnfeHI,7812
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=jQ91SBazFwGlwajveLvitMP5oLOQcuEXwUqwe0JPpd8,23203
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=oumTbJPbiLFomm5IcdKJNjP3kXVkeYTxZEtYTkBBfLA,2742
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=PGkQCeTd7ACd1aHwDWsPOQPyG5_NbC8U2wxzbldTJ1Q,17764
33
- mcp_agent/core/enhanced_prompt.py,sha256=loLFJfBgFaAxYqZednUJLLuunFiwrYQb60y1_wNJsgQ,17927
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=ttkBs2yYaZb1gsL6-iYaopSsBKUt4kVeddj16TuWZ2o,12477
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=3T7F-VcNc4_n7f9ODBcIhE1KYeFgACOdMslPG0FV624,18124
54
- mcp_agent/llm/augmented_llm_passthrough.py,sha256=_DC6lGYbXPMXBeJn9Ot2fq-fXJ5GP7HhRmlY9pNvJ2s,6033
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=y65gUc8IyX8vbdK_oOIIjauIYk5h7jyrDAdjTlRdBcc,7655
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=-qmE6CmGyB-wmFqZ3L7PyJtidw8kmwuXpt6x1XHdJVk,15856
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=ohRvAzJwZvgrN_7iKzy0RA7ILWVlOoD8KvI1c0Xr_eI,3908
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=vGMPy9ZjceqlOAnwDgm29bzjZegxChXZQHvj7OUs5Oo,4472
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=RZOMb5cJzIY1k0V28YgrHcUFSt0Uy977towy4yJE7bA,502
114
- mcp_agent/resources/examples/internal/fastagent.config.yaml,sha256=7anEzFqlMg3i5T8fEiqGYSO3fgMGI5P2BRsOJKsKl8k,1983
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.4.dist-info/METADATA,sha256=ZZc6lPJvUVd0k4Cw9nqy-tOrNjw5KtIlT_Cb8cOR0tk,29841
139
- fast_agent_mcp-0.2.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
140
- fast_agent_mcp-0.2.4.dist-info/entry_points.txt,sha256=qPM7vwtN1_KmP3dXehxgiCxUBHtqP7yfenZigztvY-w,226
141
- fast_agent_mcp-0.2.4.dist-info/licenses/LICENSE,sha256=cN3FxDURL9XuzE5mhK9L2paZo82LTfjwCYVT7e3j0e4,10939
142
- fast_agent_mcp-0.2.4.dist-info/RECORD,,
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,,
@@ -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 = self._default_request_params.model_copy() if self._default_request_params else None
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
- if _settings:
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
- config_file = Path(config_path) if config_path else Settings.find_config()
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
- pass
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
- current_dir = current_dir.parent
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()
@@ -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":
@@ -6,14 +6,15 @@ directly creates Agent instances without proxies.
6
6
 
7
7
  import argparse
8
8
  import asyncio
9
- import os
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: Optional[str] = None,
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
- if self.config_path:
134
- with open(self.config_path) as f:
135
- self.config = yaml.safe_load(f) or {}
136
- elif os.path.exists("fastagent.config.yaml"):
137
- with open("fastagent.config.yaml") as f:
138
- self.config = yaml.safe_load(f) or {}
139
- else:
140
- self.config = {}
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() as agent_app:
220
+ async with self.app.run():
170
221
  # Apply quiet mode if requested
171
222
  if (
172
223
  quiet_mode
173
- and hasattr(agent_app.context, "config")
174
- and hasattr(agent_app.context.config, "logger")
224
+ and hasattr(self.app.context, "config")
225
+ and hasattr(self.app.context.config, "logger")
175
226
  ):
176
- agent_app.context.config.logger.progress_display = False
177
- agent_app.context.config.logger.show_chat = False
178
- agent_app.context.config.logger.show_tools = False
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
- # Print the response in quiet mode
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
- from mcp_agent.mcp_server import AgentMCPServer
335
-
336
- async with self.run() as agent_app:
337
- # Create the MCP server
338
- mcp_server = AgentMCPServer(
339
- agent_app=agent_app,
340
- server_name=server_name or f"{self.name}-MCP-Server",
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
- try:
350
- # Wait for the server task to complete (or be cancelled)
351
- await server_task
352
- except asyncio.CancelledError:
353
- # Propagate cancellation
354
- server_task.cancel()
355
- await asyncio.gather(server_task, return_exceptions=True)
356
- raise
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
@@ -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 simple delimeted format.
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
- # Convert to delimited format
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
- # Write to file
446
- with open(filename, "w", encoding="utf-8") as f:
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
- return Prompt.assistant(await self.generate_str(last_message.first_text()))
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 = (
@@ -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-5-sonnet-latest",
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
- template: PromptTemplate = PromptTemplateLoader().load_from_file(file)
105
- return create_messages_with_resources(template.content_sections, [file])
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
- return PromptMessageMultipart.to_multipart(load_prompt(file))
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
- # Import the DirectAgentApp instead of AgentApp
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, agent_proxy in self.agent_app._agents.items():
30
- self.register_agent_tools(agent_name, agent_proxy)
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, agent_proxy) -> None:
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 agent_proxy.send(message)
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
- await self.mcp_server.run_sse_async()
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
- await self.mcp_server.run_stdio_async()
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.send("hello,world")
16
+ await agent()
17
17
 
18
18
 
19
19
  if __name__ == "__main__":
@@ -59,3 +59,8 @@ mcp:
59
59
  mcp_hfspace:
60
60
  command: "npx"
61
61
  args: ["@llmindset/mcp-hfspace"]
62
+
63
+ mcp_webcam:
64
+ command: "npx"
65
+ args: ["@llmindset/mcp-webcam"]
66
+
@@ -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())