fast-agent-mcp 0.2.55__py3-none-any.whl → 0.2.57__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fast-agent-mcp might be problematic. Click here for more details.
- {fast_agent_mcp-0.2.55.dist-info → fast_agent_mcp-0.2.57.dist-info}/METADATA +3 -17
- {fast_agent_mcp-0.2.55.dist-info → fast_agent_mcp-0.2.57.dist-info}/RECORD +21 -21
- mcp_agent/agents/agent.py +10 -3
- mcp_agent/agents/base_agent.py +15 -7
- mcp_agent/cli/commands/go.py +3 -3
- mcp_agent/core/agent_app.py +18 -6
- mcp_agent/core/enhanced_prompt.py +11 -2
- mcp_agent/core/fastagent.py +2 -0
- mcp_agent/core/request_params.py +5 -0
- mcp_agent/event_progress.py +3 -0
- mcp_agent/llm/augmented_llm.py +15 -0
- mcp_agent/llm/providers/augmented_llm_bedrock.py +1 -0
- mcp_agent/llm/providers/augmented_llm_groq.py +58 -29
- mcp_agent/llm/providers/augmented_llm_openai.py +4 -5
- mcp_agent/mcp/mcp_agent_client_session.py +105 -2
- mcp_agent/mcp/mcp_aggregator.py +97 -35
- mcp_agent/mcp/mcp_connection_manager.py +19 -0
- mcp_agent/ui/console_display.py +105 -15
- {fast_agent_mcp-0.2.55.dist-info → fast_agent_mcp-0.2.57.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.55.dist-info → fast_agent_mcp-0.2.57.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.2.55.dist-info → fast_agent_mcp-0.2.57.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.57
|
|
4
4
|
Summary: Define, Prompt and Test MCP enabled Agents and Workflows
|
|
5
5
|
Author-email: Shaun Smith <fastagent@llmindset.co.uk>
|
|
6
6
|
License: Apache License
|
|
@@ -211,7 +211,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
211
211
|
Requires-Python: >=3.13
|
|
212
212
|
Requires-Dist: a2a-sdk>=0.3.0
|
|
213
213
|
Requires-Dist: aiohttp>=3.11.13
|
|
214
|
-
Requires-Dist: anthropic>=0.
|
|
214
|
+
Requires-Dist: anthropic>=0.63.0
|
|
215
215
|
Requires-Dist: azure-identity>=1.14.0
|
|
216
216
|
Requires-Dist: boto3>=1.35.0
|
|
217
217
|
Requires-Dist: deprecated>=1.2.18
|
|
@@ -219,7 +219,7 @@ Requires-Dist: email-validator>=2.2.0
|
|
|
219
219
|
Requires-Dist: fastapi>=0.115.6
|
|
220
220
|
Requires-Dist: google-genai>=1.27.0
|
|
221
221
|
Requires-Dist: mcp==1.12.4
|
|
222
|
-
Requires-Dist: openai>=1.
|
|
222
|
+
Requires-Dist: openai>=1.99.9
|
|
223
223
|
Requires-Dist: opentelemetry-distro>=0.55b0
|
|
224
224
|
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.7.0
|
|
225
225
|
Requires-Dist: opentelemetry-instrumentation-anthropic>=0.43.1; python_version >= '3.10' and python_version < '4.0'
|
|
@@ -234,20 +234,6 @@ Requires-Dist: pyyaml>=6.0.2
|
|
|
234
234
|
Requires-Dist: rich>=14.1.0
|
|
235
235
|
Requires-Dist: tensorzero>=2025.7.5
|
|
236
236
|
Requires-Dist: typer>=0.15.1
|
|
237
|
-
Provides-Extra: azure
|
|
238
|
-
Requires-Dist: azure-identity>=1.14.0; extra == 'azure'
|
|
239
|
-
Provides-Extra: dev
|
|
240
|
-
Requires-Dist: anthropic>=0.42.0; extra == 'dev'
|
|
241
|
-
Requires-Dist: pre-commit>=4.0.1; extra == 'dev'
|
|
242
|
-
Requires-Dist: pydantic>=2.10.4; extra == 'dev'
|
|
243
|
-
Requires-Dist: pytest-asyncio>=0.21.1; extra == 'dev'
|
|
244
|
-
Requires-Dist: pytest-cov; extra == 'dev'
|
|
245
|
-
Requires-Dist: pytest>=7.4.0; extra == 'dev'
|
|
246
|
-
Requires-Dist: pyyaml>=6.0.2; extra == 'dev'
|
|
247
|
-
Requires-Dist: ruff>=0.8.4; extra == 'dev'
|
|
248
|
-
Requires-Dist: tomli>=2.2.1; extra == 'dev'
|
|
249
|
-
Provides-Extra: openai
|
|
250
|
-
Requires-Dist: openai>=1.58.1; extra == 'openai'
|
|
251
237
|
Description-Content-Type: text/markdown
|
|
252
238
|
|
|
253
239
|
<p align="center">
|
|
@@ -4,13 +4,13 @@ mcp_agent/config.py,sha256=BjfZmhKnoGHH80puLY90RkZPx5PPm-yvgJEqPsvvNWY,19259
|
|
|
4
4
|
mcp_agent/console.py,sha256=Gjf2QLFumwG1Lav__c07X_kZxxEUSkzV-1_-YbAwcwo,813
|
|
5
5
|
mcp_agent/context.py,sha256=lzz_Fyf9lz9BBAUt1bRVBlyyHjLkyeuyIziAi4qXYUk,7639
|
|
6
6
|
mcp_agent/context_dependent.py,sha256=QXfhw3RaQCKfscEEBRGuZ3sdMWqkgShz2jJ1ivGGX1I,1455
|
|
7
|
-
mcp_agent/event_progress.py,sha256=
|
|
7
|
+
mcp_agent/event_progress.py,sha256=YEk4RxHtblLNIn-ea8c9uHkEKOhtLEWOO662yjkf8Vo,4614
|
|
8
8
|
mcp_agent/mcp_server_registry.py,sha256=lmz-aES-l7Gbg4itDF0iCmpso_KD8bVazVKSVzjwNE4,12398
|
|
9
9
|
mcp_agent/progress_display.py,sha256=GeJU9VUt6qKsFVymG688hCMVCsAygG9ifiiEb5IcbN4,361
|
|
10
10
|
mcp_agent/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
mcp_agent/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
mcp_agent/agents/agent.py,sha256
|
|
13
|
-
mcp_agent/agents/base_agent.py,sha256=
|
|
12
|
+
mcp_agent/agents/agent.py,sha256=-vAbAXMt_0jIXyU1_rvcbGTCjwT3I0wyFL7kAdxI1Qk,3361
|
|
13
|
+
mcp_agent/agents/base_agent.py,sha256=IVQNCJMgs9gfUnhS6BvK1bVEe0ZfjzZXmWKbpI980i0,34875
|
|
14
14
|
mcp_agent/agents/workflow/__init__.py,sha256=HloteEW6kalvgR0XewpiFAqaQlMPlPJYg5p3K33IUzI,25
|
|
15
15
|
mcp_agent/agents/workflow/chain_agent.py,sha256=eIlImirrSXkqBJmPuAJgOKis81Cl6lZEGM0-6IyaUV8,6105
|
|
16
16
|
mcp_agent/agents/workflow/evaluator_optimizer.py,sha256=LT81m2B7fxgBZY0CorXFOZJbVhM5fnjDjfrcywO5UrM,12210
|
|
@@ -26,25 +26,25 @@ mcp_agent/cli/constants.py,sha256=KawdkaN289nVB02DKPB4IVUJ8-fohIUD0gLfOp0P7B8,55
|
|
|
26
26
|
mcp_agent/cli/main.py,sha256=Oo13X7LB0Cf7JrkilQXz8Eqi_48cE0Rr2qqDUOQifEQ,3175
|
|
27
27
|
mcp_agent/cli/terminal.py,sha256=GRwD-RGW7saIz2IOWZn5vD6JjiArscELBThm1GTFkuI,1065
|
|
28
28
|
mcp_agent/cli/commands/check_config.py,sha256=15YK0mtDQbVopnMm3HBjOeY2-00FUHj6tt8RvaemKmI,21081
|
|
29
|
-
mcp_agent/cli/commands/go.py,sha256=
|
|
29
|
+
mcp_agent/cli/commands/go.py,sha256=RfNJQYmuDJicpYM7oT-jfv_9P7kBqCh_5Ezd4RtcYME,14712
|
|
30
30
|
mcp_agent/cli/commands/quickstart.py,sha256=hwIr1F9zGRSQGf7kwvmirMIK7Qke2s6W95inZ-a2SMQ,21171
|
|
31
31
|
mcp_agent/cli/commands/server_helpers.py,sha256=x5tD_qhf1W4D2li09sfOyfRWCOCa6lmpumYAPsEfIQs,3649
|
|
32
32
|
mcp_agent/cli/commands/setup.py,sha256=eOEd4TL-b0DaDeSJMGOfNOsTEItoZ67W88eTP4aP-bo,6482
|
|
33
33
|
mcp_agent/cli/commands/url_parser.py,sha256=5VdtcHRHzi67YignStVbz7u-rcvNNErw9oJLAUFOtEY,5855
|
|
34
34
|
mcp_agent/core/__init__.py,sha256=7xAVBbyrsJwmrzL-we9tBY9PcJxFnajoJcaXSc6gBUw,391
|
|
35
|
-
mcp_agent/core/agent_app.py,sha256=
|
|
35
|
+
mcp_agent/core/agent_app.py,sha256=2p-wlQZJ7LNFOIxDdzt7u2p0ar-5To0laiSBCNaZWpw,16834
|
|
36
36
|
mcp_agent/core/agent_types.py,sha256=ClFN7ikstZ5suweeLFf5w9jcg3ygxVhMS7GRj9VJ8yQ,1749
|
|
37
37
|
mcp_agent/core/direct_decorators.py,sha256=mOMLHf_qhWFqcOY0QE1UbHtXAarnetIKp7n0U8WvcyM,24061
|
|
38
38
|
mcp_agent/core/direct_factory.py,sha256=0G1HbVECfdFJGn__lBYGha_qt7Xyp18ST18IOzO134E,20867
|
|
39
|
-
mcp_agent/core/enhanced_prompt.py,sha256=
|
|
39
|
+
mcp_agent/core/enhanced_prompt.py,sha256=QeBajgqu09SyK0ULEzsauAM-xlR4oLtT23udbZzVLuk,36410
|
|
40
40
|
mcp_agent/core/error_handling.py,sha256=xoyS2kLe0eG0bj2eSJCJ2odIhGUve2SbDR7jP-A-uRw,624
|
|
41
41
|
mcp_agent/core/exceptions.py,sha256=ENAD_qGG67foxy6vDkIvc-lgopIUQy6O7zvNPpPXaQg,2289
|
|
42
|
-
mcp_agent/core/fastagent.py,sha256=
|
|
42
|
+
mcp_agent/core/fastagent.py,sha256=dYN2fpgDSrk7iGHN7JmW0e8eFF47oqDmatzdUHz0eEI,25306
|
|
43
43
|
mcp_agent/core/interactive_prompt.py,sha256=Gpwn7v7m3nw7fR_W7bs5XYArrny_xJK06DOURUwpzI4,32756
|
|
44
44
|
mcp_agent/core/mcp_content.py,sha256=2D7KHY9mG_vxoDwFLKvsPQV9VRIzHItM7V-jcEnACh8,8878
|
|
45
45
|
mcp_agent/core/mermaid_utils.py,sha256=MpcRyVCPMTwU1XeIxnyFg0fQLjcyXZduWRF8NhEqvXE,5332
|
|
46
46
|
mcp_agent/core/prompt.py,sha256=qnintOUGEoDPYLI9bu9G2OlgVMCe5ZPUZilgMzydXhc,7919
|
|
47
|
-
mcp_agent/core/request_params.py,sha256=
|
|
47
|
+
mcp_agent/core/request_params.py,sha256=OW9WnQAD-I2fz2JzMsqPY2wwwHFG0SI4yAvC1WxTfNY,1735
|
|
48
48
|
mcp_agent/core/usage_display.py,sha256=CcCQbTm5ftMgkoBgr5NWzHmYFBK5cSU-FM8yiBOVzGA,7527
|
|
49
49
|
mcp_agent/core/validation.py,sha256=8D6d3mQanvzC2dXx5yc8-5bkoWEh9cxuT6-YSthSGFk,12676
|
|
50
50
|
mcp_agent/executor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -61,7 +61,7 @@ mcp_agent/human_input/handler.py,sha256=s712Z5ssTCwjL9-VKoIdP5CtgMh43YvepynYisiW
|
|
|
61
61
|
mcp_agent/human_input/simple_form.py,sha256=u6iCo39IJftB1S1xljdQP3C18RuRCcwp7jKQTTDcLT4,3441
|
|
62
62
|
mcp_agent/human_input/types.py,sha256=RtWBOVzy8vnYoQrc36jRLn8z8N3C4pDPMBN5vF6qM5Y,1476
|
|
63
63
|
mcp_agent/llm/__init__.py,sha256=d8zgwG-bRFuwiMNMYkywg_qytk4P8lawyld_meuUmHI,68
|
|
64
|
-
mcp_agent/llm/augmented_llm.py,sha256=
|
|
64
|
+
mcp_agent/llm/augmented_llm.py,sha256=ZJM3TXGV66aQ_zSDPrOG0r6EMVOOdeh1Equq8uNjDBM,28642
|
|
65
65
|
mcp_agent/llm/augmented_llm_passthrough.py,sha256=bu0DJkjyFPzBZEU7f6MHnOp__9BCYl56tFd5nZVhSeY,8808
|
|
66
66
|
mcp_agent/llm/augmented_llm_playback.py,sha256=rLzgai496e2RlxqQp_Bp0U-Y1FF1SGsWl9COx4GiCNE,5004
|
|
67
67
|
mcp_agent/llm/augmented_llm_silent.py,sha256=IUnK_1Byy4D9TG0Pj46LFeNezgSTQ8d6MQIHWAImBwE,1846
|
|
@@ -80,13 +80,13 @@ mcp_agent/llm/providers/anthropic_utils.py,sha256=vYDN5G5jKMhD2CQg8veJYab7tvvzYk
|
|
|
80
80
|
mcp_agent/llm/providers/augmented_llm_aliyun.py,sha256=Th4qeyihOSfTrojPw8YsgzEyaDwhybk7hkXkFjH-1dY,1277
|
|
81
81
|
mcp_agent/llm/providers/augmented_llm_anthropic.py,sha256=U8znA9HSCOx4FlhIjl-J29nbYc8Y-T6BXevtXuDafio,30352
|
|
82
82
|
mcp_agent/llm/providers/augmented_llm_azure.py,sha256=Xoo6dFst6L9SaGKurqptwwTUzr-sYsolZ-AFb_79puc,6098
|
|
83
|
-
mcp_agent/llm/providers/augmented_llm_bedrock.py,sha256=
|
|
83
|
+
mcp_agent/llm/providers/augmented_llm_bedrock.py,sha256=lL_au5-QdVNFr7lxQQYk0HVQ3LKUieh2HHnBQsOWoro,82312
|
|
84
84
|
mcp_agent/llm/providers/augmented_llm_deepseek.py,sha256=vphkYMFyukaejBw8SkCN-MqcG9qsfXJfWKZYYtspqPY,3877
|
|
85
85
|
mcp_agent/llm/providers/augmented_llm_generic.py,sha256=5Uq8ZBhcFuQTt7koP_5ykolREh2iWu8zKhNbh3pM9lQ,1210
|
|
86
86
|
mcp_agent/llm/providers/augmented_llm_google_native.py,sha256=c6zczfs-Iw70j3OYELHJ4S7CRwAddkeXinex_yLMhmU,22194
|
|
87
87
|
mcp_agent/llm/providers/augmented_llm_google_oai.py,sha256=g_g46h-YuxqbRZiO_dVo5zO2OkX1yx7nb6xDaQbOvWs,1137
|
|
88
|
-
mcp_agent/llm/providers/augmented_llm_groq.py,sha256=
|
|
89
|
-
mcp_agent/llm/providers/augmented_llm_openai.py,sha256=
|
|
88
|
+
mcp_agent/llm/providers/augmented_llm_groq.py,sha256=w-R1N2B7790v9tXxRCPt2SKnaB-DRdEtN_51fpdYb_I,5355
|
|
89
|
+
mcp_agent/llm/providers/augmented_llm_openai.py,sha256=v0iInSlPt939Dh2kGpGJ9_7qGpL7MhxQ7UNSc42dT6Q,25318
|
|
90
90
|
mcp_agent/llm/providers/augmented_llm_openrouter.py,sha256=m3wS83fabBOmaZJH9gQ9sFw_2TB4xTb44WCOPB-2NJ4,2001
|
|
91
91
|
mcp_agent/llm/providers/augmented_llm_tensorzero_openai.py,sha256=D53Fry2AfBLOB5z9hM19U6_HMJeVNTpiBCAJb11ulNg,5503
|
|
92
92
|
mcp_agent/llm/providers/augmented_llm_xai.py,sha256=MhlX91IUNynQ_NDknx4EQJLwg-NbR8lcHS1P4JuLOnA,1433
|
|
@@ -112,9 +112,9 @@ mcp_agent/mcp/gen_client.py,sha256=fAVwFVCgSamw4PwoWOV4wrK9TABx1S_zZv8BctRyF2k,3
|
|
|
112
112
|
mcp_agent/mcp/hf_auth.py,sha256=7szw4rkwRyK3J-sUTcVZHdwoLIZqlYo8XolJnZdjOww,4571
|
|
113
113
|
mcp_agent/mcp/interfaces.py,sha256=5y7zuXkpGCfsJfZOAEZAZtbFd5rAKjn7oG4JoblPHZ4,8070
|
|
114
114
|
mcp_agent/mcp/logger_textio.py,sha256=vljC1BtNTCxBAda9ExqNB-FwVNUZIuJT3h1nWmCjMws,3172
|
|
115
|
-
mcp_agent/mcp/mcp_agent_client_session.py,sha256=
|
|
116
|
-
mcp_agent/mcp/mcp_aggregator.py,sha256=
|
|
117
|
-
mcp_agent/mcp/mcp_connection_manager.py,sha256=
|
|
115
|
+
mcp_agent/mcp/mcp_agent_client_session.py,sha256=WUx-9usO6PHwyfodIPEtd88DNDU-1YDdvQ4TNsEpfqI,13383
|
|
116
|
+
mcp_agent/mcp/mcp_aggregator.py,sha256=9r0EOrb8UR9br6qrjwW4deWWO2Jt7NFrYJenhrePRAk,55915
|
|
117
|
+
mcp_agent/mcp/mcp_connection_manager.py,sha256=VaWH-PWjVfaCDEWT0nfMblJ_3BbvDqDgoeMtGuLS9_M,18716
|
|
118
118
|
mcp_agent/mcp/mime_utils.py,sha256=difepNR_gpb4MpMLkBRAoyhDk-AjXUHTiqKvT_VwS1o,1805
|
|
119
119
|
mcp_agent/mcp/prompt_message_multipart.py,sha256=-oSO0mnc5gkSgulE1gAntPEwAKF4asOjEeVyLjhYrEk,4336
|
|
120
120
|
mcp_agent/mcp/prompt_render.py,sha256=k3v4BZDThGE2gGiOYVQtA6x8WTEdOuXIEnRafANhN1U,2996
|
|
@@ -183,10 +183,10 @@ mcp_agent/resources/examples/workflows/router.py,sha256=vmw8aBitByi5PRFIvjYWWn2G
|
|
|
183
183
|
mcp_agent/resources/examples/workflows/short_story.md,sha256=XN9I2kzCcMmke3dE5F2lyRH5iFUZUQ8Sy-hS3rm_Wlc,1153
|
|
184
184
|
mcp_agent/resources/examples/workflows/short_story.txt,sha256=X3y_1AyhLFN2AKzCKvucJtDgAFIJfnlbsbGZO5bBWu0,1187
|
|
185
185
|
mcp_agent/tools/tool_definition.py,sha256=L3Pxl-uLEXqlVoo-bYuFTFALeI-2pIU44YgFhsTKEtM,398
|
|
186
|
-
mcp_agent/ui/console_display.py,sha256=
|
|
186
|
+
mcp_agent/ui/console_display.py,sha256=ts2oPxxprzbT9rMqZhzwq6DQkNfbq8XNwbgEPPMi7nI,32083
|
|
187
187
|
mcp_agent/ui/console_display_legacy.py,sha256=sm2v61-IPVafbF7uUaOyhO2tW_zgFWOjNS83IEWqGgI,14931
|
|
188
|
-
fast_agent_mcp-0.2.
|
|
189
|
-
fast_agent_mcp-0.2.
|
|
190
|
-
fast_agent_mcp-0.2.
|
|
191
|
-
fast_agent_mcp-0.2.
|
|
192
|
-
fast_agent_mcp-0.2.
|
|
188
|
+
fast_agent_mcp-0.2.57.dist-info/METADATA,sha256=8NJird91GzBusg3XcllAlxSRvG-fHS9KR7A2AFJowhw,30459
|
|
189
|
+
fast_agent_mcp-0.2.57.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
190
|
+
fast_agent_mcp-0.2.57.dist-info/entry_points.txt,sha256=QaX5kLdI0VdMPRdPUF1nkG_WdLUTNjp_icW6e3EhNYU,232
|
|
191
|
+
fast_agent_mcp-0.2.57.dist-info/licenses/LICENSE,sha256=Gx1L3axA4PnuK4FxsbX87jQ1opoOkSFfHHSytW6wLUU,10935
|
|
192
|
+
fast_agent_mcp-0.2.57.dist-info/RECORD,,
|
mcp_agent/agents/agent.py
CHANGED
|
@@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, TypeVar
|
|
|
10
10
|
from mcp_agent.agents.base_agent import BaseAgent
|
|
11
11
|
from mcp_agent.core.agent_types import AgentConfig
|
|
12
12
|
from mcp_agent.core.interactive_prompt import InteractivePrompt
|
|
13
|
+
from mcp_agent.core.request_params import RequestParams
|
|
13
14
|
from mcp_agent.human_input.types import HumanInputCallback
|
|
14
15
|
from mcp_agent.logging.logger import get_logger
|
|
15
16
|
from mcp_agent.mcp.interfaces import AugmentedLLMProtocol
|
|
@@ -51,13 +52,19 @@ class Agent(BaseAgent):
|
|
|
51
52
|
**kwargs,
|
|
52
53
|
)
|
|
53
54
|
|
|
54
|
-
async def prompt(
|
|
55
|
+
async def prompt(
|
|
56
|
+
self,
|
|
57
|
+
default_prompt: str = "",
|
|
58
|
+
agent_name: Optional[str] = None,
|
|
59
|
+
request_params: RequestParams | None = None
|
|
60
|
+
) -> str:
|
|
55
61
|
"""
|
|
56
62
|
Start an interactive prompt session with this agent.
|
|
57
63
|
|
|
58
64
|
Args:
|
|
59
65
|
default: Default message to use when user presses enter
|
|
60
66
|
agent_name: Ignored for single agents, included for API compatibility
|
|
67
|
+
request_params: Optional request parameters
|
|
61
68
|
|
|
62
69
|
Returns:
|
|
63
70
|
The result of the interactive session
|
|
@@ -66,14 +73,14 @@ class Agent(BaseAgent):
|
|
|
66
73
|
agent_name_str = str(self.name)
|
|
67
74
|
|
|
68
75
|
# Create agent_types dictionary with just this agent
|
|
69
|
-
agent_types = {agent_name_str: self.agent_type
|
|
76
|
+
agent_types = {agent_name_str: self.agent_type}
|
|
70
77
|
|
|
71
78
|
# Create the interactive prompt
|
|
72
79
|
prompt = InteractivePrompt(agent_types=agent_types)
|
|
73
80
|
|
|
74
81
|
# Define wrapper for send function
|
|
75
82
|
async def send_wrapper(message, agent_name):
|
|
76
|
-
return await self.send(message)
|
|
83
|
+
return await self.send(message, request_params)
|
|
77
84
|
|
|
78
85
|
# Start the prompt loop with just this agent
|
|
79
86
|
return await prompt.prompt_loop(
|
mcp_agent/agents/base_agent.py
CHANGED
|
@@ -208,7 +208,11 @@ class BaseAgent(MCPAggregator, AgentProtocol):
|
|
|
208
208
|
result: PromptMessageMultipart = await self.generate([Prompt.user(message)], request_params)
|
|
209
209
|
return result.first_text()
|
|
210
210
|
|
|
211
|
-
async def send(
|
|
211
|
+
async def send(
|
|
212
|
+
self,
|
|
213
|
+
message: Union[str, PromptMessage, PromptMessageMultipart],
|
|
214
|
+
request_params: RequestParams | None = None
|
|
215
|
+
) -> str:
|
|
212
216
|
"""
|
|
213
217
|
Send a message to the agent and get a response.
|
|
214
218
|
|
|
@@ -217,6 +221,7 @@ class BaseAgent(MCPAggregator, AgentProtocol):
|
|
|
217
221
|
- String: Converted to a user PromptMessageMultipart
|
|
218
222
|
- PromptMessage: Converted to PromptMessageMultipart
|
|
219
223
|
- PromptMessageMultipart: Used directly
|
|
224
|
+
- request_params: Optional request parameters
|
|
220
225
|
|
|
221
226
|
Returns:
|
|
222
227
|
The agent's response as a string
|
|
@@ -225,7 +230,7 @@ class BaseAgent(MCPAggregator, AgentProtocol):
|
|
|
225
230
|
prompt = self._normalize_message_input(message)
|
|
226
231
|
|
|
227
232
|
# Use the LLM to generate a response
|
|
228
|
-
response = await self.generate([prompt],
|
|
233
|
+
response = await self.generate([prompt], request_params)
|
|
229
234
|
return response.all_text()
|
|
230
235
|
|
|
231
236
|
def _normalize_message_input(
|
|
@@ -364,12 +369,15 @@ class BaseAgent(MCPAggregator, AgentProtocol):
|
|
|
364
369
|
if self.config.tools is not None:
|
|
365
370
|
filtered_tools = []
|
|
366
371
|
for tool in result.tools:
|
|
367
|
-
# Extract server name from tool name
|
|
368
|
-
|
|
369
|
-
|
|
372
|
+
# Extract server name from tool name, handling server names with hyphens
|
|
373
|
+
server_name = None
|
|
374
|
+
for configured_server in self.config.tools.keys():
|
|
375
|
+
if tool.name.startswith(f"{configured_server}-"):
|
|
376
|
+
server_name = configured_server
|
|
377
|
+
break
|
|
370
378
|
|
|
371
|
-
|
|
372
|
-
|
|
379
|
+
# Check if this server has tool filters
|
|
380
|
+
if server_name and server_name in self.config.tools:
|
|
373
381
|
# Check if tool matches any pattern for this server
|
|
374
382
|
for pattern in self.config.tools[server_name]:
|
|
375
383
|
if self._matches_pattern(tool.name, pattern, server_name):
|
mcp_agent/cli/commands/go.py
CHANGED
|
@@ -121,9 +121,9 @@ async def _run_agent(
|
|
|
121
121
|
print(response)
|
|
122
122
|
elif prompt_file:
|
|
123
123
|
prompt = load_prompt_multipart(Path(prompt_file))
|
|
124
|
-
response = await agent.generate(prompt)
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
response = await agent.agent.generate(prompt)
|
|
125
|
+
print(f"\nLoaded {len(prompt)} messages from prompt file '{prompt_file}'")
|
|
126
|
+
await agent.interactive()
|
|
127
127
|
else:
|
|
128
128
|
await agent.interactive()
|
|
129
129
|
|
mcp_agent/core/agent_app.py
CHANGED
|
@@ -11,6 +11,7 @@ from rich import print as rich_print
|
|
|
11
11
|
from mcp_agent.agents.agent import Agent
|
|
12
12
|
from mcp_agent.core.agent_types import AgentType
|
|
13
13
|
from mcp_agent.core.interactive_prompt import InteractivePrompt
|
|
14
|
+
from mcp_agent.core.request_params import RequestParams
|
|
14
15
|
from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
|
|
15
16
|
from mcp_agent.progress_display import progress_display
|
|
16
17
|
|
|
@@ -53,6 +54,7 @@ class AgentApp:
|
|
|
53
54
|
message: Union[str, PromptMessage, PromptMessageMultipart] | None = None,
|
|
54
55
|
agent_name: str | None = None,
|
|
55
56
|
default_prompt: str = "",
|
|
57
|
+
request_params: RequestParams | None = None,
|
|
56
58
|
) -> str:
|
|
57
59
|
"""
|
|
58
60
|
Make the object callable to send messages or start interactive prompt.
|
|
@@ -65,19 +67,21 @@ class AgentApp:
|
|
|
65
67
|
- PromptMessageMultipart: Used directly
|
|
66
68
|
agent_name: Optional name of the agent to send to (defaults to first agent)
|
|
67
69
|
default_prompt: Default message to use in interactive prompt mode
|
|
70
|
+
request_params: Optional request parameters including MCP metadata
|
|
68
71
|
|
|
69
72
|
Returns:
|
|
70
73
|
The agent's response as a string or the result of the interactive session
|
|
71
74
|
"""
|
|
72
75
|
if message:
|
|
73
|
-
return await self._agent(agent_name).send(message)
|
|
76
|
+
return await self._agent(agent_name).send(message, request_params)
|
|
74
77
|
|
|
75
|
-
return await self.interactive(agent_name=agent_name, default_prompt=default_prompt)
|
|
78
|
+
return await self.interactive(agent_name=agent_name, default_prompt=default_prompt, request_params=request_params)
|
|
76
79
|
|
|
77
80
|
async def send(
|
|
78
81
|
self,
|
|
79
82
|
message: Union[str, PromptMessage, PromptMessageMultipart],
|
|
80
83
|
agent_name: Optional[str] = None,
|
|
84
|
+
request_params: RequestParams | None = None,
|
|
81
85
|
) -> str:
|
|
82
86
|
"""
|
|
83
87
|
Send a message to the specified agent (or to all agents).
|
|
@@ -88,11 +92,12 @@ class AgentApp:
|
|
|
88
92
|
- PromptMessage: Converted to PromptMessageMultipart
|
|
89
93
|
- PromptMessageMultipart: Used directly
|
|
90
94
|
agent_name: Optional name of the agent to send to
|
|
95
|
+
request_params: Optional request parameters including MCP metadata
|
|
91
96
|
|
|
92
97
|
Returns:
|
|
93
98
|
The agent's response as a string
|
|
94
99
|
"""
|
|
95
|
-
return await self._agent(agent_name).send(message)
|
|
100
|
+
return await self._agent(agent_name).send(message, request_params)
|
|
96
101
|
|
|
97
102
|
def _agent(self, agent_name: str | None) -> Agent:
|
|
98
103
|
if agent_name:
|
|
@@ -233,17 +238,23 @@ class AgentApp:
|
|
|
233
238
|
)
|
|
234
239
|
|
|
235
240
|
@deprecated
|
|
236
|
-
async def prompt(
|
|
241
|
+
async def prompt(
|
|
242
|
+
self,
|
|
243
|
+
agent_name: str | None = None,
|
|
244
|
+
default_prompt: str = "",
|
|
245
|
+
request_params: RequestParams | None = None
|
|
246
|
+
) -> str:
|
|
237
247
|
"""
|
|
238
248
|
Deprecated - use interactive() instead.
|
|
239
249
|
"""
|
|
240
|
-
return await self.interactive(agent_name=agent_name, default_prompt=default_prompt)
|
|
250
|
+
return await self.interactive(agent_name=agent_name, default_prompt=default_prompt, request_params=request_params)
|
|
241
251
|
|
|
242
252
|
async def interactive(
|
|
243
253
|
self,
|
|
244
254
|
agent_name: str | None = None,
|
|
245
255
|
default_prompt: str = "",
|
|
246
256
|
pretty_print_parallel: bool = False,
|
|
257
|
+
request_params: RequestParams | None = None,
|
|
247
258
|
) -> str:
|
|
248
259
|
"""
|
|
249
260
|
Interactive prompt for sending messages with advanced features.
|
|
@@ -252,6 +263,7 @@ class AgentApp:
|
|
|
252
263
|
agent_name: Optional target agent name (uses default if not specified)
|
|
253
264
|
default: Default message to use when user presses enter
|
|
254
265
|
pretty_print_parallel: Enable clean parallel results display for parallel agents
|
|
266
|
+
request_params: Optional request parameters including MCP metadata
|
|
255
267
|
|
|
256
268
|
Returns:
|
|
257
269
|
The result of the interactive session
|
|
@@ -285,7 +297,7 @@ class AgentApp:
|
|
|
285
297
|
|
|
286
298
|
# Define the wrapper for send function
|
|
287
299
|
async def send_wrapper(message, agent_name):
|
|
288
|
-
result = await self.send(message, agent_name)
|
|
300
|
+
result = await self.send(message, agent_name, request_params)
|
|
289
301
|
|
|
290
302
|
# Show parallel results if enabled and this is a parallel agent
|
|
291
303
|
if pretty_print_parallel:
|
|
@@ -428,9 +428,14 @@ def create_keybindings(on_toggle_multiline=None, app=None, agent_provider=None,
|
|
|
428
428
|
"""Enter: accept input when not in multiline mode."""
|
|
429
429
|
event.current_buffer.validate_and_handle()
|
|
430
430
|
|
|
431
|
+
@kb.add("c-j", filter=Condition(lambda: not in_multiline_mode))
|
|
432
|
+
def _(event) -> None:
|
|
433
|
+
"""Ctrl+J: Insert newline when in normal mode."""
|
|
434
|
+
event.current_buffer.insert_text("\n")
|
|
435
|
+
|
|
431
436
|
@kb.add("c-m", filter=Condition(lambda: in_multiline_mode))
|
|
432
437
|
def _(event) -> None:
|
|
433
|
-
"""Enter:
|
|
438
|
+
"""Enter: Insert newline when in multiline mode."""
|
|
434
439
|
event.current_buffer.insert_text("\n")
|
|
435
440
|
|
|
436
441
|
# Use c-j (Ctrl+J) as an alternative to represent Ctrl+Enter in multiline mode
|
|
@@ -581,13 +586,15 @@ async def get_enhanced_input(
|
|
|
581
586
|
|
|
582
587
|
shortcuts = [
|
|
583
588
|
("Ctrl+T", toggle_text),
|
|
589
|
+
("Ctrl+J", "Newline" if not in_multiline_mode else None),
|
|
584
590
|
("Ctrl+E", "External"),
|
|
585
591
|
("Ctrl+Y", "Copy"),
|
|
586
592
|
("Ctrl+L", "Clear"),
|
|
587
593
|
("↑/↓", "History"),
|
|
594
|
+
("EXIT", "Exit")
|
|
588
595
|
]
|
|
589
596
|
|
|
590
|
-
newline = "Ctrl
|
|
597
|
+
newline = "Ctrl+J:Submit" if in_multiline_mode else "<Enter>:Submit"
|
|
591
598
|
|
|
592
599
|
# Only show relevant shortcuts based on mode
|
|
593
600
|
shortcuts = [(k, v) for k, v in shortcuts if v]
|
|
@@ -669,6 +676,8 @@ async def get_enhanced_input(
|
|
|
669
676
|
def pre_process_input(text):
|
|
670
677
|
# Command processing
|
|
671
678
|
if text and text.startswith("/"):
|
|
679
|
+
if text == "/":
|
|
680
|
+
return ""
|
|
672
681
|
cmd_parts = text[1:].strip().split(maxsplit=1)
|
|
673
682
|
cmd = cmd_parts[0].lower()
|
|
674
683
|
|
mcp_agent/core/fastagent.py
CHANGED
|
@@ -87,6 +87,7 @@ class FastAgent:
|
|
|
87
87
|
ignore_unknown_args: bool = False,
|
|
88
88
|
parse_cli_args: bool = True,
|
|
89
89
|
quiet: bool = False, # Add quiet parameter
|
|
90
|
+
**kwargs
|
|
90
91
|
) -> None:
|
|
91
92
|
"""
|
|
92
93
|
Initialize the fast-agent application.
|
|
@@ -203,6 +204,7 @@ class FastAgent:
|
|
|
203
204
|
self.app = MCPApp(
|
|
204
205
|
name=name,
|
|
205
206
|
settings=config.Settings(**self.config) if hasattr(self, "config") else None,
|
|
207
|
+
**kwargs
|
|
206
208
|
)
|
|
207
209
|
|
|
208
210
|
# Stop progress display immediately if quiet mode is requested
|
mcp_agent/core/request_params.py
CHANGED
|
@@ -52,3 +52,8 @@ class RequestParams(CreateMessageRequestParams):
|
|
|
52
52
|
"""
|
|
53
53
|
Optional dictionary of template variables for dynamic templates. Currently only works for TensorZero inference backend
|
|
54
54
|
"""
|
|
55
|
+
|
|
56
|
+
mcp_metadata: Dict[str, Any] | None = None
|
|
57
|
+
"""
|
|
58
|
+
Metadata to pass through to MCP tool calls via the _meta field.
|
|
59
|
+
"""
|
mcp_agent/event_progress.py
CHANGED
mcp_agent/llm/augmented_llm.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
|
+
from contextvars import ContextVar
|
|
2
3
|
from typing import (
|
|
3
4
|
TYPE_CHECKING,
|
|
4
5
|
Any,
|
|
@@ -61,6 +62,9 @@ if TYPE_CHECKING:
|
|
|
61
62
|
# TODO -- move this to a constant
|
|
62
63
|
HUMAN_INPUT_TOOL_NAME = "__human_input__"
|
|
63
64
|
|
|
65
|
+
# Context variable for storing MCP metadata
|
|
66
|
+
_mcp_metadata_var: ContextVar[Dict[str, Any] | None] = ContextVar('mcp_metadata', default=None)
|
|
67
|
+
|
|
64
68
|
|
|
65
69
|
def deep_merge(dict1: Dict[Any, Any], dict2: Dict[Any, Any]) -> Dict[Any, Any]:
|
|
66
70
|
"""
|
|
@@ -232,6 +236,11 @@ class AugmentedLLM(ContextDependent, AugmentedLLMProtocol, Generic[MessageParamT
|
|
|
232
236
|
|
|
233
237
|
self._precall(multipart_messages)
|
|
234
238
|
|
|
239
|
+
# Store MCP metadata in context variable
|
|
240
|
+
final_request_params = self.get_request_params(request_params)
|
|
241
|
+
if final_request_params.mcp_metadata:
|
|
242
|
+
_mcp_metadata_var.set(final_request_params.mcp_metadata)
|
|
243
|
+
|
|
235
244
|
assistant_response: PromptMessageMultipart = await self._apply_prompt_provider_specific(
|
|
236
245
|
multipart_messages, request_params
|
|
237
246
|
)
|
|
@@ -275,6 +284,12 @@ class AugmentedLLM(ContextDependent, AugmentedLLMProtocol, Generic[MessageParamT
|
|
|
275
284
|
multipart_messages = PromptMessageMultipart.to_multipart(multipart_messages)
|
|
276
285
|
|
|
277
286
|
self._precall(multipart_messages)
|
|
287
|
+
|
|
288
|
+
# Store MCP metadata in context variable
|
|
289
|
+
final_request_params = self.get_request_params(request_params)
|
|
290
|
+
if final_request_params.mcp_metadata:
|
|
291
|
+
_mcp_metadata_var.set(final_request_params.mcp_metadata)
|
|
292
|
+
|
|
278
293
|
result, assistant_response = await self._apply_prompt_provider_specific_structured(
|
|
279
294
|
multipart_messages, model, request_params
|
|
280
295
|
)
|
|
@@ -49,35 +49,22 @@ class GroqAugmentedLLM(OpenAIAugmentedLLM):
|
|
|
49
49
|
if "object" == json_mode:
|
|
50
50
|
request_params.response_format = {"type": "json_object"}
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
format_description += "}"
|
|
69
|
-
|
|
70
|
-
multipart_messages[-1].add_text(
|
|
71
|
-
f"""YOU MUST RESPOND WITH A JSON OBJECT IN EXACTLY THIS FORMAT:
|
|
72
|
-
{format_description}
|
|
73
|
-
|
|
74
|
-
IMPORTANT RULES:
|
|
75
|
-
- Respond ONLY with the JSON object, no other text
|
|
76
|
-
- Do NOT include "properties" or "schema" wrappers
|
|
77
|
-
- Do NOT use code fences or markdown
|
|
78
|
-
- The response must be valid JSON that matches the format above
|
|
79
|
-
- All required fields must be included"""
|
|
80
|
-
)
|
|
52
|
+
# Create a cleaner format description from full schema
|
|
53
|
+
full_schema = model.model_json_schema()
|
|
54
|
+
format_description = self._schema_to_json_object(
|
|
55
|
+
full_schema, full_schema.get("$defs")
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
multipart_messages[-1].add_text(
|
|
59
|
+
f"""YOU MUST RESPOND WITH A JSON OBJECT IN EXACTLY THIS FORMAT:
|
|
60
|
+
{format_description}
|
|
61
|
+
|
|
62
|
+
IMPORTANT RULES:
|
|
63
|
+
- Respond ONLY with the JSON object, no other text
|
|
64
|
+
- Do NOT include "properties" or "schema" wrappers
|
|
65
|
+
- Do NOT use code fences or markdown
|
|
66
|
+
- The response must be valid JSON that matches the format above
|
|
67
|
+
- All required fields must be included""")
|
|
81
68
|
|
|
82
69
|
result: PromptMessageMultipart = await self._apply_prompt_provider_specific(
|
|
83
70
|
multipart_messages, request_params
|
|
@@ -101,3 +88,45 @@ class GroqAugmentedLLM(OpenAIAugmentedLLM):
|
|
|
101
88
|
base_url = self.context.config.groq.base_url
|
|
102
89
|
|
|
103
90
|
return base_url if base_url else GROQ_BASE_URL
|
|
91
|
+
|
|
92
|
+
def _schema_to_json_object(
|
|
93
|
+
self, schema: dict, defs: dict | None = None, visited: set | None = None
|
|
94
|
+
) -> str:
|
|
95
|
+
visited = visited or set()
|
|
96
|
+
|
|
97
|
+
if id(schema) in visited:
|
|
98
|
+
return '"<recursive>"'
|
|
99
|
+
visited.add(id(schema))
|
|
100
|
+
|
|
101
|
+
if "$ref" in schema:
|
|
102
|
+
ref = schema.get("$ref", "")
|
|
103
|
+
if ref.startswith("#/$defs/"):
|
|
104
|
+
target = ref.split("/")[-1]
|
|
105
|
+
if defs and target in defs:
|
|
106
|
+
return self._schema_to_json_object(defs[target], defs, visited)
|
|
107
|
+
return f'"<ref:{ref}>"'
|
|
108
|
+
|
|
109
|
+
schema_type = schema.get("type")
|
|
110
|
+
description = schema.get("description", "")
|
|
111
|
+
required = schema.get("required", [])
|
|
112
|
+
|
|
113
|
+
if schema_type == "object":
|
|
114
|
+
props = schema.get("properties", {})
|
|
115
|
+
result = "{\n"
|
|
116
|
+
for prop_name, prop_schema in props.items():
|
|
117
|
+
is_required = prop_name in required
|
|
118
|
+
prop_str = self._schema_to_json_object(prop_schema, defs, visited)
|
|
119
|
+
if is_required:
|
|
120
|
+
prop_str += " // REQUIRED"
|
|
121
|
+
result += f' "{prop_name}": {prop_str},\n'
|
|
122
|
+
result += "}"
|
|
123
|
+
return result
|
|
124
|
+
elif schema_type == "array":
|
|
125
|
+
items = schema.get("items", {})
|
|
126
|
+
items_str = self._schema_to_json_object(items, defs, visited)
|
|
127
|
+
return f"[{items_str}]"
|
|
128
|
+
elif schema_type:
|
|
129
|
+
comment = f" // {description}" if description else ""
|
|
130
|
+
return f'"{schema_type}"' + comment
|
|
131
|
+
|
|
132
|
+
return '"<unknown>"'
|
|
@@ -171,7 +171,6 @@ class OpenAIAugmentedLLM(AugmentedLLM[ChatCompletionMessageParam, ChatCompletion
|
|
|
171
171
|
async def _process_stream_manual(self, stream, model: str):
|
|
172
172
|
"""Manual stream processing for providers like Ollama that may not work with ChatCompletionStreamState."""
|
|
173
173
|
from openai.types.chat import ChatCompletionMessageToolCall
|
|
174
|
-
from openai.types.chat.chat_completion_message_tool_call import Function
|
|
175
174
|
|
|
176
175
|
# Track estimated output tokens by counting text chunks
|
|
177
176
|
estimated_tokens = 0
|
|
@@ -249,10 +248,10 @@ class OpenAIAugmentedLLM(AugmentedLLM[ChatCompletionMessageParam, ChatCompletion
|
|
|
249
248
|
ChatCompletionMessageToolCall(
|
|
250
249
|
id=tool_call_data["id"],
|
|
251
250
|
type=tool_call_data["type"],
|
|
252
|
-
function=
|
|
253
|
-
name
|
|
254
|
-
arguments
|
|
255
|
-
|
|
251
|
+
function={
|
|
252
|
+
"name": tool_call_data["function"]["name"],
|
|
253
|
+
"arguments": tool_call_data["function"]["arguments"],
|
|
254
|
+
},
|
|
256
255
|
)
|
|
257
256
|
)
|
|
258
257
|
|
|
@@ -14,8 +14,17 @@ from mcp.shared.session import (
|
|
|
14
14
|
SendRequestT,
|
|
15
15
|
)
|
|
16
16
|
from mcp.types import (
|
|
17
|
+
CallToolRequest,
|
|
18
|
+
CallToolRequestParams,
|
|
19
|
+
CallToolResult,
|
|
20
|
+
GetPromptRequest,
|
|
21
|
+
GetPromptRequestParams,
|
|
22
|
+
GetPromptResult,
|
|
17
23
|
Implementation,
|
|
18
24
|
ListRootsResult,
|
|
25
|
+
ReadResourceRequest,
|
|
26
|
+
ReadResourceRequestParams,
|
|
27
|
+
ReadResourceResult,
|
|
19
28
|
Root,
|
|
20
29
|
ToolListChangedNotification,
|
|
21
30
|
)
|
|
@@ -180,8 +189,17 @@ class MCPAgentClientSession(ClientSession, ContextDependent):
|
|
|
180
189
|
)
|
|
181
190
|
return result
|
|
182
191
|
except Exception as e:
|
|
183
|
-
|
|
184
|
-
|
|
192
|
+
# Handle connection errors cleanly
|
|
193
|
+
from anyio import ClosedResourceError
|
|
194
|
+
|
|
195
|
+
if isinstance(e, ClosedResourceError):
|
|
196
|
+
# Show clean offline message and convert to ConnectionError
|
|
197
|
+
from mcp_agent import console
|
|
198
|
+
console.console.print(f"[dim red]MCP server {self.session_server_name} offline[/dim red]")
|
|
199
|
+
raise ConnectionError(f"MCP server {self.session_server_name} offline") from e
|
|
200
|
+
else:
|
|
201
|
+
logger.error(f"send_request failed: {str(e)}")
|
|
202
|
+
raise
|
|
185
203
|
|
|
186
204
|
async def _received_notification(self, notification: ServerNotification) -> None:
|
|
187
205
|
"""
|
|
@@ -226,3 +244,88 @@ class MCPAgentClientSession(ClientSession, ContextDependent):
|
|
|
226
244
|
await self._tool_list_changed_callback(server_name)
|
|
227
245
|
except Exception as e:
|
|
228
246
|
logger.error(f"Error in tool list changed callback: {e}")
|
|
247
|
+
|
|
248
|
+
async def call_tool(
|
|
249
|
+
self,
|
|
250
|
+
name: str,
|
|
251
|
+
arguments: dict | None = None,
|
|
252
|
+
_meta: dict | None = None,
|
|
253
|
+
**kwargs
|
|
254
|
+
) -> CallToolResult:
|
|
255
|
+
"""Call a tool with optional metadata support."""
|
|
256
|
+
if _meta:
|
|
257
|
+
from mcp.types import RequestParams
|
|
258
|
+
|
|
259
|
+
# Safe merge - preserve existing meta fields like progressToken
|
|
260
|
+
existing_meta = kwargs.get('meta')
|
|
261
|
+
if existing_meta:
|
|
262
|
+
meta_dict = existing_meta.model_dump() if hasattr(existing_meta, 'model_dump') else {}
|
|
263
|
+
meta_dict.update(_meta)
|
|
264
|
+
meta_obj = RequestParams.Meta(**meta_dict)
|
|
265
|
+
else:
|
|
266
|
+
meta_obj = RequestParams.Meta(**_meta)
|
|
267
|
+
|
|
268
|
+
# Create CallToolRequestParams without meta, then add _meta via model_dump
|
|
269
|
+
params = CallToolRequestParams(name=name, arguments=arguments)
|
|
270
|
+
params_dict = params.model_dump(by_alias=True)
|
|
271
|
+
params_dict["_meta"] = meta_obj.model_dump()
|
|
272
|
+
|
|
273
|
+
# Create request with proper types
|
|
274
|
+
request = CallToolRequest(
|
|
275
|
+
method="tools/call",
|
|
276
|
+
params=CallToolRequestParams.model_validate(params_dict)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
return await self.send_request(request, CallToolResult)
|
|
280
|
+
else:
|
|
281
|
+
return await super().call_tool(name, arguments, **kwargs)
|
|
282
|
+
|
|
283
|
+
async def read_resource(self, uri: str, _meta: dict | None = None, **kwargs) -> ReadResourceResult:
|
|
284
|
+
"""Read a resource with optional metadata support."""
|
|
285
|
+
if _meta:
|
|
286
|
+
from mcp.types import RequestParams
|
|
287
|
+
|
|
288
|
+
# Safe merge - preserve existing meta fields like progressToken
|
|
289
|
+
existing_meta = kwargs.get('meta')
|
|
290
|
+
if existing_meta:
|
|
291
|
+
meta_dict = existing_meta.model_dump() if hasattr(existing_meta, 'model_dump') else {}
|
|
292
|
+
meta_dict.update(_meta)
|
|
293
|
+
meta_obj = RequestParams.Meta(**meta_dict)
|
|
294
|
+
else:
|
|
295
|
+
meta_obj = RequestParams.Meta(**_meta)
|
|
296
|
+
|
|
297
|
+
request = ReadResourceRequest(
|
|
298
|
+
method="resources/read",
|
|
299
|
+
params=ReadResourceRequestParams(uri=uri, meta=meta_obj)
|
|
300
|
+
)
|
|
301
|
+
return await self.send_request(request, ReadResourceResult)
|
|
302
|
+
else:
|
|
303
|
+
return await super().read_resource(uri, **kwargs)
|
|
304
|
+
|
|
305
|
+
async def get_prompt(
|
|
306
|
+
self,
|
|
307
|
+
name: str,
|
|
308
|
+
arguments: dict | None = None,
|
|
309
|
+
_meta: dict | None = None,
|
|
310
|
+
**kwargs
|
|
311
|
+
) -> GetPromptResult:
|
|
312
|
+
"""Get a prompt with optional metadata support."""
|
|
313
|
+
if _meta:
|
|
314
|
+
from mcp.types import RequestParams
|
|
315
|
+
|
|
316
|
+
# Safe merge - preserve existing meta fields like progressToken
|
|
317
|
+
existing_meta = kwargs.get('meta')
|
|
318
|
+
if existing_meta:
|
|
319
|
+
meta_dict = existing_meta.model_dump() if hasattr(existing_meta, 'model_dump') else {}
|
|
320
|
+
meta_dict.update(_meta)
|
|
321
|
+
meta_obj = RequestParams.Meta(**meta_dict)
|
|
322
|
+
else:
|
|
323
|
+
meta_obj = RequestParams.Meta(**_meta)
|
|
324
|
+
|
|
325
|
+
request = GetPromptRequest(
|
|
326
|
+
method="prompts/get",
|
|
327
|
+
params=GetPromptRequestParams(name=name, arguments=arguments, meta=meta_obj)
|
|
328
|
+
)
|
|
329
|
+
return await self.send_request(request, GetPromptResult)
|
|
330
|
+
else:
|
|
331
|
+
return await super().get_prompt(name, arguments, **kwargs)
|
mcp_agent/mcp/mcp_aggregator.py
CHANGED
|
@@ -139,7 +139,10 @@ class MCPAggregator(ContextDependent):
|
|
|
139
139
|
|
|
140
140
|
def _create_progress_callback(self, server_name: str, tool_name: str) -> "ProgressFnT":
|
|
141
141
|
"""Create a progress callback function for tool execution."""
|
|
142
|
-
|
|
142
|
+
|
|
143
|
+
async def progress_callback(
|
|
144
|
+
progress: float, total: float | None, message: str | None
|
|
145
|
+
) -> None:
|
|
143
146
|
"""Handle progress notifications from MCP tool execution."""
|
|
144
147
|
logger.info(
|
|
145
148
|
"Tool progress update",
|
|
@@ -153,6 +156,7 @@ class MCPAggregator(ContextDependent):
|
|
|
153
156
|
"details": message or "", # Put the message in details column
|
|
154
157
|
},
|
|
155
158
|
)
|
|
159
|
+
|
|
156
160
|
return progress_callback
|
|
157
161
|
|
|
158
162
|
async def close(self) -> None:
|
|
@@ -508,12 +512,28 @@ class MCPAggregator(ContextDependent):
|
|
|
508
512
|
async def try_execute(client: ClientSession):
|
|
509
513
|
try:
|
|
510
514
|
method = getattr(client, method_name)
|
|
515
|
+
|
|
516
|
+
# Get metadata from context for tool, resource, and prompt calls
|
|
517
|
+
metadata = None
|
|
518
|
+
if method_name in ["call_tool", "read_resource", "get_prompt"]:
|
|
519
|
+
from mcp_agent.llm.augmented_llm import _mcp_metadata_var
|
|
520
|
+
|
|
521
|
+
metadata = _mcp_metadata_var.get()
|
|
522
|
+
|
|
523
|
+
# Prepare kwargs
|
|
524
|
+
kwargs = method_args or {}
|
|
525
|
+
if metadata:
|
|
526
|
+
kwargs["_meta"] = metadata
|
|
527
|
+
|
|
511
528
|
# For call_tool method, check if we need to add progress_callback
|
|
512
529
|
if method_name == "call_tool" and progress_callback:
|
|
513
530
|
# The call_tool method signature includes progress_callback parameter
|
|
514
|
-
return await method(
|
|
531
|
+
return await method(progress_callback=progress_callback, **kwargs)
|
|
515
532
|
else:
|
|
516
|
-
return await method(**
|
|
533
|
+
return await method(**(kwargs or {}))
|
|
534
|
+
except ConnectionError:
|
|
535
|
+
# Let ConnectionError pass through for reconnection logic
|
|
536
|
+
raise
|
|
517
537
|
except Exception as e:
|
|
518
538
|
error_msg = (
|
|
519
539
|
f"Failed to {method_name} '{operation_name}' on server '{server_name}': {e}"
|
|
@@ -525,34 +545,77 @@ class MCPAggregator(ContextDependent):
|
|
|
525
545
|
# Re-raise the original exception to propagate it
|
|
526
546
|
raise e
|
|
527
547
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
data={
|
|
537
|
-
"progress_action": ProgressAction.STARTING,
|
|
538
|
-
"server_name": server_name,
|
|
539
|
-
"agent_name": self.agent_name,
|
|
540
|
-
},
|
|
541
|
-
)
|
|
542
|
-
async with gen_client(
|
|
543
|
-
server_name, server_registry=self.context.server_registry
|
|
544
|
-
) as client:
|
|
545
|
-
result = await try_execute(client)
|
|
548
|
+
# Try initial execution
|
|
549
|
+
try:
|
|
550
|
+
if self.connection_persistence:
|
|
551
|
+
server_connection = await self._persistent_connection_manager.get_server(
|
|
552
|
+
server_name, client_session_factory=MCPAgentClientSession
|
|
553
|
+
)
|
|
554
|
+
return await try_execute(server_connection.session)
|
|
555
|
+
else:
|
|
546
556
|
logger.debug(
|
|
547
|
-
f"
|
|
557
|
+
f"Creating temporary connection to server: {server_name}",
|
|
548
558
|
data={
|
|
549
|
-
"progress_action": ProgressAction.
|
|
559
|
+
"progress_action": ProgressAction.STARTING,
|
|
550
560
|
"server_name": server_name,
|
|
551
561
|
"agent_name": self.agent_name,
|
|
552
562
|
},
|
|
553
563
|
)
|
|
564
|
+
async with gen_client(
|
|
565
|
+
server_name, server_registry=self.context.server_registry
|
|
566
|
+
) as client:
|
|
567
|
+
result = await try_execute(client)
|
|
568
|
+
logger.debug(
|
|
569
|
+
f"Closing temporary connection to server: {server_name}",
|
|
570
|
+
data={
|
|
571
|
+
"progress_action": ProgressAction.SHUTDOWN,
|
|
572
|
+
"server_name": server_name,
|
|
573
|
+
"agent_name": self.agent_name,
|
|
574
|
+
},
|
|
575
|
+
)
|
|
576
|
+
return result
|
|
577
|
+
except ConnectionError:
|
|
578
|
+
# Server offline - attempt reconnection
|
|
579
|
+
from mcp_agent import console
|
|
580
|
+
|
|
581
|
+
console.console.print(
|
|
582
|
+
f"[dim yellow]MCP server {server_name} reconnecting...[/dim yellow]"
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
try:
|
|
586
|
+
if self.connection_persistence:
|
|
587
|
+
# Force disconnect and create fresh connection
|
|
588
|
+
await self._persistent_connection_manager.disconnect_server(server_name)
|
|
589
|
+
import asyncio
|
|
590
|
+
|
|
591
|
+
await asyncio.sleep(0.1)
|
|
592
|
+
|
|
593
|
+
server_connection = await self._persistent_connection_manager.get_server(
|
|
594
|
+
server_name, client_session_factory=MCPAgentClientSession
|
|
595
|
+
)
|
|
596
|
+
result = await try_execute(server_connection.session)
|
|
597
|
+
else:
|
|
598
|
+
# For non-persistent connections, just try again
|
|
599
|
+
async with gen_client(
|
|
600
|
+
server_name, server_registry=self.context.server_registry
|
|
601
|
+
) as client:
|
|
602
|
+
result = await try_execute(client)
|
|
603
|
+
|
|
604
|
+
# Success!
|
|
605
|
+
console.console.print(f"[dim green]MCP server {server_name} online[/dim green]")
|
|
554
606
|
return result
|
|
555
607
|
|
|
608
|
+
except Exception:
|
|
609
|
+
# Reconnection failed
|
|
610
|
+
console.console.print(
|
|
611
|
+
f"[dim red]MCP server {server_name} offline - failed to reconnect[/dim red]"
|
|
612
|
+
)
|
|
613
|
+
error_msg = f"MCP server {server_name} offline - failed to reconnect"
|
|
614
|
+
if error_factory:
|
|
615
|
+
return error_factory(error_msg)
|
|
616
|
+
else:
|
|
617
|
+
raise Exception(error_msg)
|
|
618
|
+
|
|
556
619
|
async def _parse_resource_name(self, name: str, resource_type: str) -> tuple[str, str]:
|
|
557
620
|
"""
|
|
558
621
|
Parse a possibly namespaced resource name into server name and local resource name.
|
|
@@ -572,14 +635,13 @@ class MCPAggregator(ContextDependent):
|
|
|
572
635
|
|
|
573
636
|
# Next, attempt to interpret as a namespaced name
|
|
574
637
|
if is_namespaced_name(name):
|
|
575
|
-
|
|
576
|
-
server_name
|
|
638
|
+
# Try to match against known server names, handling server names with hyphens
|
|
639
|
+
for server_name in self.server_names:
|
|
640
|
+
if name.startswith(f"{server_name}{SEP}"):
|
|
641
|
+
local_name = name[len(server_name) + len(SEP) :]
|
|
642
|
+
return server_name, local_name
|
|
577
643
|
|
|
578
|
-
#
|
|
579
|
-
if server_name in self.server_names:
|
|
580
|
-
return server_name, local_name
|
|
581
|
-
|
|
582
|
-
# If the server name doesn't exist, it might be a tool with a hyphen in its name
|
|
644
|
+
# If no server name matched, it might be a tool with a hyphen in its name
|
|
583
645
|
# Fall through to the next checks
|
|
584
646
|
|
|
585
647
|
# For tools, search all servers for the tool by exact name match
|
|
@@ -623,10 +685,10 @@ class MCPAggregator(ContextDependent):
|
|
|
623
685
|
with tracer.start_as_current_span(f"MCP Tool: {server_name}/{local_tool_name}"):
|
|
624
686
|
trace.get_current_span().set_attribute("tool_name", local_tool_name)
|
|
625
687
|
trace.get_current_span().set_attribute("server_name", server_name)
|
|
626
|
-
|
|
688
|
+
|
|
627
689
|
# Create progress callback for this tool execution
|
|
628
690
|
progress_callback = self._create_progress_callback(server_name, local_tool_name)
|
|
629
|
-
|
|
691
|
+
|
|
630
692
|
return await self._execute_on_server(
|
|
631
693
|
server_name=server_name,
|
|
632
694
|
operation_type="tool",
|
|
@@ -1236,11 +1298,11 @@ class MCPAggregator(ContextDependent):
|
|
|
1236
1298
|
async def list_mcp_tools(self, server_name: str | None = None) -> Dict[str, List[Tool]]:
|
|
1237
1299
|
"""
|
|
1238
1300
|
List available tools from one or all servers, grouped by server name.
|
|
1239
|
-
|
|
1301
|
+
|
|
1240
1302
|
Args:
|
|
1241
1303
|
server_name: Optional server name to list tools from. If not provided,
|
|
1242
1304
|
lists tools from all servers.
|
|
1243
|
-
|
|
1305
|
+
|
|
1244
1306
|
Returns:
|
|
1245
1307
|
Dictionary mapping server names to lists of Tool objects (with original names, not namespaced)
|
|
1246
1308
|
"""
|
|
@@ -1249,7 +1311,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1249
1311
|
|
|
1250
1312
|
results: Dict[str, List[Tool]] = {}
|
|
1251
1313
|
|
|
1252
|
-
# Get the list of servers to check
|
|
1314
|
+
# Get the list of servers to check
|
|
1253
1315
|
servers_to_check = [server_name] if server_name else self.server_names
|
|
1254
1316
|
|
|
1255
1317
|
# For each server, try to list its tools
|
|
@@ -272,6 +272,7 @@ class MCPConnectionManager(ContextDependent):
|
|
|
272
272
|
# Manage our own task group - independent of task context
|
|
273
273
|
self._task_group = None
|
|
274
274
|
self._task_group_active = False
|
|
275
|
+
self._mcp_sse_filter_added = False
|
|
275
276
|
|
|
276
277
|
async def __aenter__(self):
|
|
277
278
|
# Create a task group that isn't tied to a specific task
|
|
@@ -300,6 +301,21 @@ class MCPConnectionManager(ContextDependent):
|
|
|
300
301
|
except Exception as e:
|
|
301
302
|
logger.error(f"Error during connection manager shutdown: {e}")
|
|
302
303
|
|
|
304
|
+
def _suppress_mcp_sse_errors(self) -> None:
|
|
305
|
+
"""Suppress MCP library's 'Error in sse_reader' messages."""
|
|
306
|
+
if self._mcp_sse_filter_added:
|
|
307
|
+
return
|
|
308
|
+
|
|
309
|
+
import logging
|
|
310
|
+
|
|
311
|
+
class MCPSSEErrorFilter(logging.Filter):
|
|
312
|
+
def filter(self, record):
|
|
313
|
+
return not (record.name == "mcp.client.sse" and "Error in sse_reader" in record.getMessage())
|
|
314
|
+
|
|
315
|
+
mcp_sse_logger = logging.getLogger("mcp.client.sse")
|
|
316
|
+
mcp_sse_logger.addFilter(MCPSSEErrorFilter())
|
|
317
|
+
self._mcp_sse_filter_added = True
|
|
318
|
+
|
|
303
319
|
async def launch_server(
|
|
304
320
|
self,
|
|
305
321
|
server_name: str,
|
|
@@ -341,6 +357,9 @@ class MCPConnectionManager(ContextDependent):
|
|
|
341
357
|
logger.debug(f"{server_name}: Creating stdio client with custom error handler")
|
|
342
358
|
return _add_none_to_context(stdio_client(server_params, errlog=error_handler))
|
|
343
359
|
elif config.transport == "sse":
|
|
360
|
+
# Suppress MCP library error spam
|
|
361
|
+
self._suppress_mcp_sse_errors()
|
|
362
|
+
|
|
344
363
|
return _add_none_to_context(
|
|
345
364
|
sse_client(
|
|
346
365
|
config.url,
|
mcp_agent/ui/console_display.py
CHANGED
|
@@ -19,6 +19,61 @@ from mcp_agent.mcp.mcp_aggregator import MCPAggregator
|
|
|
19
19
|
HUMAN_INPUT_TOOL_NAME = "__human_input__"
|
|
20
20
|
CODE_STYLE = "native"
|
|
21
21
|
|
|
22
|
+
HTML_ESCAPE_CHARS = {"&": "&", "<": "<", ">": ">", '"': """, "'": "'"}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _prepare_markdown_content(content: str, escape_xml: bool = True) -> str:
|
|
26
|
+
"""Prepare content for markdown rendering by escaping HTML/XML tags
|
|
27
|
+
while preserving code blocks and inline code.
|
|
28
|
+
|
|
29
|
+
This ensures XML/HTML tags are displayed as visible text rather than
|
|
30
|
+
being interpreted as markup by the markdown renderer.
|
|
31
|
+
|
|
32
|
+
Note: This method does not handle overlapping code blocks (e.g., if inline
|
|
33
|
+
code appears within a fenced code block range). In practice, this is not
|
|
34
|
+
an issue since markdown syntax doesn't support such overlapping.
|
|
35
|
+
"""
|
|
36
|
+
if not escape_xml or not isinstance(content, str):
|
|
37
|
+
return content
|
|
38
|
+
|
|
39
|
+
protected_ranges = []
|
|
40
|
+
import re
|
|
41
|
+
|
|
42
|
+
# Protect fenced code blocks (don't escape anything inside these)
|
|
43
|
+
code_block_pattern = r"```[\s\S]*?```"
|
|
44
|
+
for match in re.finditer(code_block_pattern, content):
|
|
45
|
+
protected_ranges.append((match.start(), match.end()))
|
|
46
|
+
|
|
47
|
+
# Protect inline code (don't escape anything inside these)
|
|
48
|
+
inline_code_pattern = r"(?<!`)`(?!``)[^`\n]+`(?!`)"
|
|
49
|
+
for match in re.finditer(inline_code_pattern, content):
|
|
50
|
+
protected_ranges.append((match.start(), match.end()))
|
|
51
|
+
|
|
52
|
+
protected_ranges.sort(key=lambda x: x[0])
|
|
53
|
+
|
|
54
|
+
# Build the escaped content
|
|
55
|
+
result = []
|
|
56
|
+
last_end = 0
|
|
57
|
+
|
|
58
|
+
for start, end in protected_ranges:
|
|
59
|
+
# Escape everything outside protected ranges
|
|
60
|
+
unprotected_text = content[last_end:start]
|
|
61
|
+
for char, replacement in HTML_ESCAPE_CHARS.items():
|
|
62
|
+
unprotected_text = unprotected_text.replace(char, replacement)
|
|
63
|
+
result.append(unprotected_text)
|
|
64
|
+
|
|
65
|
+
# Keep protected ranges (code blocks) as-is
|
|
66
|
+
result.append(content[start:end])
|
|
67
|
+
last_end = end
|
|
68
|
+
|
|
69
|
+
# Escape any remaining content after the last protected range
|
|
70
|
+
remainder_text = content[last_end:]
|
|
71
|
+
for char, replacement in HTML_ESCAPE_CHARS.items():
|
|
72
|
+
remainder_text = remainder_text.replace(char, replacement)
|
|
73
|
+
result.append(remainder_text)
|
|
74
|
+
|
|
75
|
+
return "".join(result)
|
|
76
|
+
|
|
22
77
|
|
|
23
78
|
class ConsoleDisplay:
|
|
24
79
|
"""
|
|
@@ -35,6 +90,49 @@ class ConsoleDisplay:
|
|
|
35
90
|
"""
|
|
36
91
|
self.config = config
|
|
37
92
|
self._markup = config.logger.enable_markup if config else True
|
|
93
|
+
self._escape_xml = True
|
|
94
|
+
|
|
95
|
+
def _render_content_smartly(self, content: str, check_markdown_markers: bool = False) -> None:
|
|
96
|
+
"""
|
|
97
|
+
Helper method to intelligently render content based on its type.
|
|
98
|
+
|
|
99
|
+
- Pure XML: Use syntax highlighting for readability
|
|
100
|
+
- Markdown (with markers): Use markdown rendering with proper escaping
|
|
101
|
+
- Plain text: Display as-is (when check_markdown_markers=True and no markers found)
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
content: The text content to render
|
|
105
|
+
check_markdown_markers: If True, only use markdown rendering when markers are present
|
|
106
|
+
"""
|
|
107
|
+
import re
|
|
108
|
+
|
|
109
|
+
from rich.markdown import Markdown
|
|
110
|
+
|
|
111
|
+
# Check if content appears to be primarily XML
|
|
112
|
+
xml_pattern = r"^<[a-zA-Z_][a-zA-Z0-9_-]*[^>]*>"
|
|
113
|
+
is_xml_content = bool(re.match(xml_pattern, content.strip())) and content.count("<") > 5
|
|
114
|
+
|
|
115
|
+
if is_xml_content:
|
|
116
|
+
# Display XML content with syntax highlighting for better readability
|
|
117
|
+
from rich.syntax import Syntax
|
|
118
|
+
|
|
119
|
+
syntax = Syntax(content, "xml", theme=CODE_STYLE, line_numbers=False)
|
|
120
|
+
console.console.print(syntax, markup=self._markup)
|
|
121
|
+
elif check_markdown_markers:
|
|
122
|
+
# Check for markdown markers before deciding to use markdown rendering
|
|
123
|
+
if any(marker in content for marker in ["##", "**", "*", "`", "---", "###"]):
|
|
124
|
+
# Has markdown markers - render as markdown with escaping
|
|
125
|
+
prepared_content = _prepare_markdown_content(content, self._escape_xml)
|
|
126
|
+
md = Markdown(prepared_content, code_theme=CODE_STYLE)
|
|
127
|
+
console.console.print(md, markup=self._markup)
|
|
128
|
+
else:
|
|
129
|
+
# Plain text - display as-is
|
|
130
|
+
console.console.print(content, markup=self._markup)
|
|
131
|
+
else:
|
|
132
|
+
# Always treat as markdown with proper escaping
|
|
133
|
+
prepared_content = _prepare_markdown_content(content, self._escape_xml)
|
|
134
|
+
md = Markdown(prepared_content, code_theme=CODE_STYLE)
|
|
135
|
+
console.console.print(md, markup=self._markup)
|
|
38
136
|
|
|
39
137
|
def show_tool_result(self, result: CallToolResult, name: str | None = None) -> None:
|
|
40
138
|
"""Display a tool result in the new visual style."""
|
|
@@ -341,7 +439,6 @@ class ConsoleDisplay:
|
|
|
341
439
|
model: str | None = None,
|
|
342
440
|
) -> None:
|
|
343
441
|
"""Display an assistant message in a formatted panel."""
|
|
344
|
-
from rich.markdown import Markdown
|
|
345
442
|
|
|
346
443
|
if not self.config or not self.config.logger.show_chat:
|
|
347
444
|
return
|
|
@@ -385,9 +482,8 @@ class ConsoleDisplay:
|
|
|
385
482
|
json = JSON(message_text)
|
|
386
483
|
console.console.print(json, markup=self._markup)
|
|
387
484
|
except (JSONDecodeError, TypeError, ValueError):
|
|
388
|
-
#
|
|
389
|
-
|
|
390
|
-
console.console.print(md, markup=self._markup)
|
|
485
|
+
# Use the smart rendering helper to handle XML vs Markdown
|
|
486
|
+
self._render_content_smartly(content)
|
|
391
487
|
else:
|
|
392
488
|
# Handle Rich Text objects directly
|
|
393
489
|
console.console.print(message_text, markup=self._markup)
|
|
@@ -475,7 +571,6 @@ class ConsoleDisplay:
|
|
|
475
571
|
self, message, model: str | None = None, chat_turn: int = 0, name: str | None = None
|
|
476
572
|
) -> None:
|
|
477
573
|
"""Display a user message in the new visual style."""
|
|
478
|
-
from rich.markdown import Markdown
|
|
479
574
|
|
|
480
575
|
if not self.config or not self.config.logger.show_chat:
|
|
481
576
|
return
|
|
@@ -495,9 +590,8 @@ class ConsoleDisplay:
|
|
|
495
590
|
|
|
496
591
|
# Display content as markdown if it looks like markdown, otherwise as text
|
|
497
592
|
if isinstance(message, str):
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
console.console.print(md, markup=self._markup)
|
|
593
|
+
# Use the smart rendering helper to handle XML vs Markdown
|
|
594
|
+
self._render_content_smartly(message)
|
|
501
595
|
else:
|
|
502
596
|
# Handle Text objects directly
|
|
503
597
|
console.console.print(message, markup=self._markup)
|
|
@@ -587,7 +681,7 @@ class ConsoleDisplay:
|
|
|
587
681
|
Args:
|
|
588
682
|
parallel_agent: The parallel agent containing fan_out_agents with results
|
|
589
683
|
"""
|
|
590
|
-
|
|
684
|
+
|
|
591
685
|
from rich.text import Text
|
|
592
686
|
|
|
593
687
|
if self.config and not self.config.logger.show_chat:
|
|
@@ -674,13 +768,9 @@ class ConsoleDisplay:
|
|
|
674
768
|
console.console.print(left + " " * padding + right, markup=self._markup)
|
|
675
769
|
console.console.print()
|
|
676
770
|
|
|
677
|
-
# Display content
|
|
771
|
+
# Display content based on its type (check for markdown markers in parallel results)
|
|
678
772
|
content = result["content"]
|
|
679
|
-
|
|
680
|
-
md = Markdown(content, code_theme=CODE_STYLE)
|
|
681
|
-
console.console.print(md, markup=self._markup)
|
|
682
|
-
else:
|
|
683
|
-
console.console.print(content, markup=self._markup)
|
|
773
|
+
self._render_content_smartly(content, check_markdown_markers=True)
|
|
684
774
|
|
|
685
775
|
# Summary
|
|
686
776
|
console.console.print()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|