fast-agent-mcp 0.2.45__py3-none-any.whl → 0.2.46__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.45.dist-info → fast_agent_mcp-0.2.46.dist-info}/METADATA +2 -2
- {fast_agent_mcp-0.2.45.dist-info → fast_agent_mcp-0.2.46.dist-info}/RECORD +18 -15
- mcp_agent/__init__.py +40 -0
- mcp_agent/cli/commands/go.py +25 -4
- mcp_agent/core/__init__.py +26 -0
- mcp_agent/core/direct_decorators.py +114 -16
- mcp_agent/human_input/__init__.py +50 -0
- mcp_agent/human_input/form_fields.py +252 -0
- mcp_agent/human_input/simple_form.py +111 -0
- mcp_agent/llm/augmented_llm.py +11 -2
- mcp_agent/llm/augmented_llm_playback.py +5 -3
- mcp_agent/mcp/__init__.py +50 -0
- mcp_agent/mcp/helpers/__init__.py +23 -1
- mcp_agent/mcp/interfaces.py +13 -2
- mcp_agent/py.typed +0 -0
- {fast_agent_mcp-0.2.45.dist-info → fast_agent_mcp-0.2.46.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.45.dist-info → fast_agent_mcp-0.2.46.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.2.45.dist-info → fast_agent_mcp-0.2.46.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.46
|
|
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
|
|
@@ -218,7 +218,7 @@ Requires-Dist: deprecated>=1.2.18
|
|
|
218
218
|
Requires-Dist: email-validator>=2.2.0
|
|
219
219
|
Requires-Dist: fastapi>=0.115.6
|
|
220
220
|
Requires-Dist: google-genai
|
|
221
|
-
Requires-Dist: mcp==1.12.
|
|
221
|
+
Requires-Dist: mcp==1.12.1
|
|
222
222
|
Requires-Dist: openai>=1.93.0
|
|
223
223
|
Requires-Dist: opentelemetry-distro>=0.50b0
|
|
224
224
|
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.29.0
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
mcp_agent/__init__.py,sha256=
|
|
1
|
+
mcp_agent/__init__.py,sha256=HWWxZeB-VxrUNNXZnu4duzKGwdfCdD2M_O6drN4kfs8,2389
|
|
2
2
|
mcp_agent/app.py,sha256=3mtHP1nRQcRaKhhxgTmCOv00alh70nT7UxNA8bN47QE,5560
|
|
3
3
|
mcp_agent/config.py,sha256=RjwgvR-Sys4JIzhNyEsaS_NarCe157RenJLOsioUtDk,18980
|
|
4
4
|
mcp_agent/console.py,sha256=Gjf2QLFumwG1Lav__c07X_kZxxEUSkzV-1_-YbAwcwo,813
|
|
@@ -7,6 +7,7 @@ mcp_agent/context_dependent.py,sha256=QXfhw3RaQCKfscEEBRGuZ3sdMWqkgShz2jJ1ivGGX1
|
|
|
7
7
|
mcp_agent/event_progress.py,sha256=d7T1hQ1D289MYh2Z5bMPB4JqjGqTOzveJuOHE03B_Xo,3720
|
|
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
|
+
mcp_agent/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
11
|
mcp_agent/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
12
|
mcp_agent/agents/agent.py,sha256=EAYlcP1qqI1D0_CS808I806z1048FBjZQxxpcCZPeIU,3154
|
|
12
13
|
mcp_agent/agents/base_agent.py,sha256=VCBWJ-l1zMQiBuONGzqcbqPUQfXK4oq-pBB5lJrMgQ0,32318
|
|
@@ -24,15 +25,15 @@ mcp_agent/cli/constants.py,sha256=KawdkaN289nVB02DKPB4IVUJ8-fohIUD0gLfOp0P7B8,55
|
|
|
24
25
|
mcp_agent/cli/main.py,sha256=Hfa6yn47gfx_d6TUEbzDz68k2gaIS9vvN0yDbVOUgnc,3181
|
|
25
26
|
mcp_agent/cli/terminal.py,sha256=GRwD-RGW7saIz2IOWZn5vD6JjiArscELBThm1GTFkuI,1065
|
|
26
27
|
mcp_agent/cli/commands/check_config.py,sha256=15YK0mtDQbVopnMm3HBjOeY2-00FUHj6tt8RvaemKmI,21081
|
|
27
|
-
mcp_agent/cli/commands/go.py,sha256=
|
|
28
|
+
mcp_agent/cli/commands/go.py,sha256=ydVEyLrMxkp0ZuiPbcOhxUuxBx5FE2RJAV02dppSkaU,14121
|
|
28
29
|
mcp_agent/cli/commands/quickstart.py,sha256=lcozUGP9RRO8xZaayJg4pQNeY5zDQs-eg-ABm0A15cI,16471
|
|
29
30
|
mcp_agent/cli/commands/server_helpers.py,sha256=x5tD_qhf1W4D2li09sfOyfRWCOCa6lmpumYAPsEfIQs,3649
|
|
30
31
|
mcp_agent/cli/commands/setup.py,sha256=eOEd4TL-b0DaDeSJMGOfNOsTEItoZ67W88eTP4aP-bo,6482
|
|
31
32
|
mcp_agent/cli/commands/url_parser.py,sha256=5VdtcHRHzi67YignStVbz7u-rcvNNErw9oJLAUFOtEY,5855
|
|
32
|
-
mcp_agent/core/__init__.py,sha256=
|
|
33
|
+
mcp_agent/core/__init__.py,sha256=7xAVBbyrsJwmrzL-we9tBY9PcJxFnajoJcaXSc6gBUw,391
|
|
33
34
|
mcp_agent/core/agent_app.py,sha256=SolGwejEmv9XtsTsmiMkNKPia7RN1VcHXm6JoEo4hvQ,16187
|
|
34
35
|
mcp_agent/core/agent_types.py,sha256=7zVzAFWjvh5dDV3TuDwmO9LAWmDjYnZd3eeLH-wvvIQ,1705
|
|
35
|
-
mcp_agent/core/direct_decorators.py,sha256=
|
|
36
|
+
mcp_agent/core/direct_decorators.py,sha256=CTM9KbzpLx-tPiT5KJN7nZycCyc74ekMJy3wdZhysmo,22065
|
|
36
37
|
mcp_agent/core/direct_factory.py,sha256=d_HvbAxyv2WrM07zyCpLXFVn7eArXk1LZmLKS49hzJo,19537
|
|
37
38
|
mcp_agent/core/enhanced_prompt.py,sha256=ZIeJCeW7rcGMBZ2OdEQwqOmRT0wNSp0hO2-dZRSnnLE,36068
|
|
38
39
|
mcp_agent/core/error_handling.py,sha256=xoyS2kLe0eG0bj2eSJCJ2odIhGUve2SbDR7jP-A-uRw,624
|
|
@@ -48,17 +49,19 @@ mcp_agent/executor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
|
|
|
48
49
|
mcp_agent/executor/executor.py,sha256=E44p6d-o3OMRoP_dNs_cDnyti91LQ3P9eNU88mSi1kc,9462
|
|
49
50
|
mcp_agent/executor/task_registry.py,sha256=PCALFeYtkQrPBg4RBJnlA0aDI8nHclrNkHGUS4kV3W8,1242
|
|
50
51
|
mcp_agent/executor/workflow_signal.py,sha256=Cg1uZBk3fn8kXhPOg-wINNuVaf3v9pvLD6NbqWy5Z6E,11142
|
|
51
|
-
mcp_agent/human_input/__init__.py,sha256=
|
|
52
|
+
mcp_agent/human_input/__init__.py,sha256=RydNC10PhvTYFbLXXs0JMTnAt0ihPYeJZoRTnsq3eMg,909
|
|
52
53
|
mcp_agent/human_input/elicitation_form.py,sha256=VgS-DXlwYTU4qDntok4Pqt8qfl1w_-Xby5PTlfNerug,28324
|
|
53
54
|
mcp_agent/human_input/elicitation_forms.py,sha256=w8XQ1GfZX8Jw-VB4jnDI0Im4mF-T9Ts8mT2zRZBtL6M,3824
|
|
54
55
|
mcp_agent/human_input/elicitation_handler.py,sha256=YfVhIhSBc9wuszPS4zoHho4n1pwmIoq13huN4MSRkIs,3305
|
|
55
56
|
mcp_agent/human_input/elicitation_state.py,sha256=Unl9uhEybUqACCUimnETdfUprJNpYDMq3DdbbHw5oAw,1175
|
|
57
|
+
mcp_agent/human_input/form_fields.py,sha256=aE7HdR-wOPO_6HllNaJXtn3BzpPsC4TctUApbveRk8g,7644
|
|
56
58
|
mcp_agent/human_input/handler.py,sha256=s712Z5ssTCwjL9-VKoIdP5CtgMh43YvepynYisiWTTA,3144
|
|
59
|
+
mcp_agent/human_input/simple_form.py,sha256=u6iCo39IJftB1S1xljdQP3C18RuRCcwp7jKQTTDcLT4,3441
|
|
57
60
|
mcp_agent/human_input/types.py,sha256=RtWBOVzy8vnYoQrc36jRLn8z8N3C4pDPMBN5vF6qM5Y,1476
|
|
58
61
|
mcp_agent/llm/__init__.py,sha256=d8zgwG-bRFuwiMNMYkywg_qytk4P8lawyld_meuUmHI,68
|
|
59
|
-
mcp_agent/llm/augmented_llm.py,sha256=
|
|
62
|
+
mcp_agent/llm/augmented_llm.py,sha256=UCAKqlsv3eUBIhOz0p3_bSNKOY3MykJIb7OBKoXJjWI,27973
|
|
60
63
|
mcp_agent/llm/augmented_llm_passthrough.py,sha256=bu0DJkjyFPzBZEU7f6MHnOp__9BCYl56tFd5nZVhSeY,8808
|
|
61
|
-
mcp_agent/llm/augmented_llm_playback.py,sha256=
|
|
64
|
+
mcp_agent/llm/augmented_llm_playback.py,sha256=rLzgai496e2RlxqQp_Bp0U-Y1FF1SGsWl9COx4GiCNE,5004
|
|
62
65
|
mcp_agent/llm/augmented_llm_silent.py,sha256=IUnK_1Byy4D9TG0Pj46LFeNezgSTQ8d6MQIHWAImBwE,1846
|
|
63
66
|
mcp_agent/llm/augmented_llm_slow.py,sha256=DDSD8bL2flmQrVHZm-UDs7sR8aHRWkDOcOW-mX_GPok,2067
|
|
64
67
|
mcp_agent/llm/memory.py,sha256=pTOaTDV3EA3X68yKwEtUAu7s0xGIQQ_cKBhfYUnfR0w,8614
|
|
@@ -99,13 +102,13 @@ mcp_agent/logging/listeners.py,sha256=_S4Jp5_KWp0kUfrx4BxDdNCeQK3MNT3Zi9AaolPri7
|
|
|
99
102
|
mcp_agent/logging/logger.py,sha256=v2_D5kWLSS9u4ueSU7q6cWF1oSmTVeAAtgnwR0LrbXI,11056
|
|
100
103
|
mcp_agent/logging/rich_progress.py,sha256=uMAKDPO8TlhT7uTeJzOEwQPme8pu8qCADLNbsI1vV8g,5544
|
|
101
104
|
mcp_agent/logging/transport.py,sha256=_RVckOdcs_mXv6Jwz-MPe0vVTQEKsbejHdteyK5hBQA,16960
|
|
102
|
-
mcp_agent/mcp/__init__.py,sha256=
|
|
105
|
+
mcp_agent/mcp/__init__.py,sha256=yxtzSmWBNqRf_nJckqgzYoozyGKja05FvSRLLus_7eM,1100
|
|
103
106
|
mcp_agent/mcp/common.py,sha256=MpSC0fLO21RcDz4VApah4C8_LisVGz7OXkR17Xw-9mY,431
|
|
104
107
|
mcp_agent/mcp/elicitation_factory.py,sha256=gY0gEsF8Jdg01nSsrVbbl62ZS1A725QgDRB6UDPCadc,3162
|
|
105
108
|
mcp_agent/mcp/elicitation_handlers.py,sha256=w2S4kBn05pIKdq2-X13MErinFg5jSElwFsoTuW3zFSs,6618
|
|
106
109
|
mcp_agent/mcp/gen_client.py,sha256=fAVwFVCgSamw4PwoWOV4wrK9TABx1S_zZv8BctRyF2k,3030
|
|
107
110
|
mcp_agent/mcp/hf_auth.py,sha256=7szw4rkwRyK3J-sUTcVZHdwoLIZqlYo8XolJnZdjOww,4571
|
|
108
|
-
mcp_agent/mcp/interfaces.py,sha256=
|
|
111
|
+
mcp_agent/mcp/interfaces.py,sha256=pe8WKvu3dnNnPWmrkDlu15xdkhuRDAjLUMn2vIE0Mj8,7963
|
|
109
112
|
mcp_agent/mcp/logger_textio.py,sha256=vljC1BtNTCxBAda9ExqNB-FwVNUZIuJT3h1nWmCjMws,3172
|
|
110
113
|
mcp_agent/mcp/mcp_agent_client_session.py,sha256=nEHrSalG5z47BKGwE9ooOmlSTc4Gb7qdEut071Dwepo,8987
|
|
111
114
|
mcp_agent/mcp/mcp_aggregator.py,sha256=D1xFBVVq_4Tn1Ka16SZTVB3qeU-FO5501ITHGXTuXs0,49730
|
|
@@ -116,7 +119,7 @@ mcp_agent/mcp/prompt_render.py,sha256=k3v4BZDThGE2gGiOYVQtA6x8WTEdOuXIEnRafANhN1
|
|
|
116
119
|
mcp_agent/mcp/prompt_serialization.py,sha256=MQY6QxnhQTiq0oBDsyRzFtX8sBiovUjzUFX78As8q60,17974
|
|
117
120
|
mcp_agent/mcp/resource_utils.py,sha256=K4XY8bihmBMleRTZ2viMPiD2Y2HWxFnlgIJi6dd_PYE,6588
|
|
118
121
|
mcp_agent/mcp/sampling.py,sha256=PpUtLDvu9K3U8445z6m_esHeKstdSr-JBTyn9d8ppJM,6665
|
|
119
|
-
mcp_agent/mcp/helpers/__init__.py,sha256=
|
|
122
|
+
mcp_agent/mcp/helpers/__init__.py,sha256=jJP_yVlewL0hePTX_k_MAnUmOzxpr0Tr_rXYZYeE2WY,456
|
|
120
123
|
mcp_agent/mcp/helpers/content_helpers.py,sha256=_P5xfVpJg4F7lBM-v6-1Bjtvfgr9UfTQoW2FSxyxil4,4112
|
|
121
124
|
mcp_agent/mcp/helpers/server_config_helpers.py,sha256=MkyZB2ZzfsBNsqyGd0LfUOoXxhAMS28VF-f747cJJpY,978
|
|
122
125
|
mcp_agent/mcp/prompts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -162,8 +165,8 @@ mcp_agent/resources/examples/workflows/short_story.txt,sha256=X3y_1AyhLFN2AKzCKv
|
|
|
162
165
|
mcp_agent/tools/tool_definition.py,sha256=L3Pxl-uLEXqlVoo-bYuFTFALeI-2pIU44YgFhsTKEtM,398
|
|
163
166
|
mcp_agent/ui/console_display.py,sha256=cv-dvpJT7-zd7d8nqlcDLKfYg9_pDEQQOt7rGnDvj54,26057
|
|
164
167
|
mcp_agent/ui/console_display_legacy.py,sha256=sm2v61-IPVafbF7uUaOyhO2tW_zgFWOjNS83IEWqGgI,14931
|
|
165
|
-
fast_agent_mcp-0.2.
|
|
166
|
-
fast_agent_mcp-0.2.
|
|
167
|
-
fast_agent_mcp-0.2.
|
|
168
|
-
fast_agent_mcp-0.2.
|
|
169
|
-
fast_agent_mcp-0.2.
|
|
168
|
+
fast_agent_mcp-0.2.46.dist-info/METADATA,sha256=nJUU501B28wTMPXWrVCWA5Tgo7M_Eem1c_thiUB6aLM,31041
|
|
169
|
+
fast_agent_mcp-0.2.46.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
170
|
+
fast_agent_mcp-0.2.46.dist-info/entry_points.txt,sha256=QaX5kLdI0VdMPRdPUF1nkG_WdLUTNjp_icW6e3EhNYU,232
|
|
171
|
+
fast_agent_mcp-0.2.46.dist-info/licenses/LICENSE,sha256=Gx1L3axA4PnuK4FxsbX87jQ1opoOkSFfHHSytW6wLUU,10935
|
|
172
|
+
fast_agent_mcp-0.2.46.dist-info/RECORD,,
|
mcp_agent/__init__.py
CHANGED
|
@@ -31,9 +31,32 @@ from mcp_agent.core.direct_decorators import (
|
|
|
31
31
|
# FastAgent components
|
|
32
32
|
from mcp_agent.core.fastagent import FastAgent
|
|
33
33
|
|
|
34
|
+
# MCP content creation utilities
|
|
35
|
+
from mcp_agent.core.mcp_content import (
|
|
36
|
+
Assistant,
|
|
37
|
+
MCPFile,
|
|
38
|
+
MCPImage,
|
|
39
|
+
MCPPrompt,
|
|
40
|
+
MCPText,
|
|
41
|
+
User,
|
|
42
|
+
create_message,
|
|
43
|
+
)
|
|
44
|
+
|
|
34
45
|
# Request configuration
|
|
35
46
|
from mcp_agent.core.request_params import RequestParams
|
|
36
47
|
|
|
48
|
+
# MCP content helpers
|
|
49
|
+
from mcp_agent.mcp.helpers import (
|
|
50
|
+
get_image_data,
|
|
51
|
+
get_resource_text,
|
|
52
|
+
get_resource_uri,
|
|
53
|
+
get_text,
|
|
54
|
+
is_image_content,
|
|
55
|
+
is_resource_content,
|
|
56
|
+
is_resource_link,
|
|
57
|
+
is_text_content,
|
|
58
|
+
)
|
|
59
|
+
|
|
37
60
|
# Core protocol interfaces
|
|
38
61
|
from mcp_agent.mcp.interfaces import AgentProtocol, AugmentedLLMProtocol
|
|
39
62
|
from mcp_agent.mcp.mcp_aggregator import MCPAggregator
|
|
@@ -71,4 +94,21 @@ __all__ = [
|
|
|
71
94
|
"evaluator_optimizer",
|
|
72
95
|
# Request configuration
|
|
73
96
|
"RequestParams",
|
|
97
|
+
# MCP content helpers
|
|
98
|
+
"get_text",
|
|
99
|
+
"get_image_data",
|
|
100
|
+
"get_resource_uri",
|
|
101
|
+
"is_text_content",
|
|
102
|
+
"is_image_content",
|
|
103
|
+
"is_resource_content",
|
|
104
|
+
"is_resource_link",
|
|
105
|
+
"get_resource_text",
|
|
106
|
+
# MCP content creation utilities
|
|
107
|
+
"MCPText",
|
|
108
|
+
"MCPImage",
|
|
109
|
+
"MCPFile",
|
|
110
|
+
"MCPPrompt",
|
|
111
|
+
"User",
|
|
112
|
+
"Assistant",
|
|
113
|
+
"create_message",
|
|
74
114
|
]
|
mcp_agent/cli/commands/go.py
CHANGED
|
@@ -259,8 +259,8 @@ def run_async_agent(
|
|
|
259
259
|
def go(
|
|
260
260
|
ctx: typer.Context,
|
|
261
261
|
name: str = typer.Option("FastAgent CLI", "--name", help="Name for the agent"),
|
|
262
|
-
instruction: str = typer.Option(
|
|
263
|
-
|
|
262
|
+
instruction: Optional[str] = typer.Option(
|
|
263
|
+
None, "--instruction", "-i", help="Path to file or URL containing instruction for the agent"
|
|
264
264
|
),
|
|
265
265
|
config_path: Optional[str] = typer.Option(
|
|
266
266
|
None, "--config-path", "-c", help="Path to config file"
|
|
@@ -297,7 +297,8 @@ def go(
|
|
|
297
297
|
Run an interactive agent directly from the command line.
|
|
298
298
|
|
|
299
299
|
Examples:
|
|
300
|
-
fast-agent go --model=haiku --instruction
|
|
300
|
+
fast-agent go --model=haiku --instruction=./instruction.md --servers=fetch,filesystem
|
|
301
|
+
fast-agent go --instruction=https://raw.githubusercontent.com/user/repo/prompt.md
|
|
301
302
|
fast-agent go --message="What is the weather today?" --model=haiku
|
|
302
303
|
fast-agent go --prompt-file=my-prompt.txt --model=haiku
|
|
303
304
|
fast-agent go --url=http://localhost:8001/mcp,http://api.example.com/sse
|
|
@@ -335,9 +336,29 @@ def go(
|
|
|
335
336
|
if stdio:
|
|
336
337
|
stdio_commands.append(stdio)
|
|
337
338
|
|
|
339
|
+
# Resolve instruction from file/URL or use default
|
|
340
|
+
resolved_instruction = "You are a helpful AI Agent." # Default
|
|
341
|
+
if instruction:
|
|
342
|
+
try:
|
|
343
|
+
from pathlib import Path
|
|
344
|
+
|
|
345
|
+
from pydantic import AnyUrl
|
|
346
|
+
|
|
347
|
+
from mcp_agent.core.direct_decorators import _resolve_instruction
|
|
348
|
+
|
|
349
|
+
# Check if it's a URL
|
|
350
|
+
if instruction.startswith(("http://", "https://")):
|
|
351
|
+
resolved_instruction = _resolve_instruction(AnyUrl(instruction))
|
|
352
|
+
else:
|
|
353
|
+
# Treat as file path
|
|
354
|
+
resolved_instruction = _resolve_instruction(Path(instruction))
|
|
355
|
+
except Exception as e:
|
|
356
|
+
typer.echo(f"Error loading instruction from {instruction}: {e}", err=True)
|
|
357
|
+
raise typer.Exit(1)
|
|
358
|
+
|
|
338
359
|
run_async_agent(
|
|
339
360
|
name=name,
|
|
340
|
-
instruction=
|
|
361
|
+
instruction=resolved_instruction,
|
|
341
362
|
config_path=config_path,
|
|
342
363
|
servers=servers,
|
|
343
364
|
urls=urls,
|
mcp_agent/core/__init__.py
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core components and utilities for MCP Agent.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .mcp_content import (
|
|
6
|
+
Assistant,
|
|
7
|
+
MCPContentType,
|
|
8
|
+
MCPFile,
|
|
9
|
+
MCPImage,
|
|
10
|
+
MCPPrompt,
|
|
11
|
+
MCPText,
|
|
12
|
+
User,
|
|
13
|
+
create_message,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
# MCP content creation functions
|
|
18
|
+
"MCPText",
|
|
19
|
+
"MCPImage",
|
|
20
|
+
"MCPFile",
|
|
21
|
+
"MCPPrompt",
|
|
22
|
+
"User",
|
|
23
|
+
"Assistant",
|
|
24
|
+
"create_message",
|
|
25
|
+
"MCPContentType",
|
|
26
|
+
]
|
|
@@ -6,6 +6,7 @@ for creating agents in the DirectFastAgent framework.
|
|
|
6
6
|
|
|
7
7
|
import inspect
|
|
8
8
|
from functools import wraps
|
|
9
|
+
from pathlib import Path
|
|
9
10
|
from typing import (
|
|
10
11
|
Awaitable,
|
|
11
12
|
Callable,
|
|
@@ -21,6 +22,7 @@ from typing import (
|
|
|
21
22
|
)
|
|
22
23
|
|
|
23
24
|
from mcp.client.session import ElicitationFnT
|
|
25
|
+
from pydantic import AnyUrl
|
|
24
26
|
|
|
25
27
|
from mcp_agent.agents.agent import AgentConfig
|
|
26
28
|
from mcp_agent.agents.workflow.router_agent import (
|
|
@@ -85,6 +87,91 @@ class DecoratedEvaluatorOptimizerProtocol(DecoratedAgentProtocol[P, R], Protocol
|
|
|
85
87
|
_evaluator: str
|
|
86
88
|
|
|
87
89
|
|
|
90
|
+
def _fetch_url_content(url: str) -> str:
|
|
91
|
+
"""
|
|
92
|
+
Fetch content from a URL.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
url: The URL to fetch content from
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
The text content from the URL
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
requests.RequestException: If the URL cannot be fetched
|
|
102
|
+
UnicodeDecodeError: If the content cannot be decoded as UTF-8
|
|
103
|
+
"""
|
|
104
|
+
import requests
|
|
105
|
+
|
|
106
|
+
response = requests.get(url, timeout=10)
|
|
107
|
+
response.raise_for_status() # Raise exception for HTTP errors
|
|
108
|
+
return response.text
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _apply_templates(text: str) -> str:
|
|
112
|
+
"""
|
|
113
|
+
Apply template substitutions to instruction text.
|
|
114
|
+
|
|
115
|
+
Supported templates:
|
|
116
|
+
{{currentDate}} - Current date in format "24 July 2025"
|
|
117
|
+
{{url:https://...}} - Content fetched from the specified URL
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
text: The text to process
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Text with template substitutions applied
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
requests.RequestException: If a URL in {{url:...}} cannot be fetched
|
|
127
|
+
UnicodeDecodeError: If URL content cannot be decoded as UTF-8
|
|
128
|
+
"""
|
|
129
|
+
import re
|
|
130
|
+
from datetime import datetime
|
|
131
|
+
|
|
132
|
+
# Apply {{currentDate}} template
|
|
133
|
+
current_date = datetime.now().strftime("%d %B %Y")
|
|
134
|
+
text = text.replace("{{currentDate}}", current_date)
|
|
135
|
+
|
|
136
|
+
# Apply {{url:...}} templates
|
|
137
|
+
url_pattern = re.compile(r"\{\{url:(https?://[^}]+)\}\}")
|
|
138
|
+
|
|
139
|
+
def replace_url(match):
|
|
140
|
+
url = match.group(1)
|
|
141
|
+
return _fetch_url_content(url)
|
|
142
|
+
|
|
143
|
+
text = url_pattern.sub(replace_url, text)
|
|
144
|
+
|
|
145
|
+
return text
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _resolve_instruction(instruction: str | Path | AnyUrl) -> str:
|
|
149
|
+
"""
|
|
150
|
+
Resolve instruction from either a string, Path, or URL with template support.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
instruction: Either a string instruction, Path to a file, or URL containing the instruction
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
The resolved instruction string with templates applied
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
FileNotFoundError: If the Path doesn't exist
|
|
160
|
+
PermissionError: If the Path can't be read
|
|
161
|
+
UnicodeDecodeError: If the file/URL content can't be decoded as UTF-8
|
|
162
|
+
requests.RequestException: If the URL cannot be fetched
|
|
163
|
+
"""
|
|
164
|
+
if isinstance(instruction, Path):
|
|
165
|
+
text = instruction.read_text(encoding="utf-8")
|
|
166
|
+
elif isinstance(instruction, AnyUrl):
|
|
167
|
+
text = _fetch_url_content(str(instruction))
|
|
168
|
+
else:
|
|
169
|
+
text = instruction
|
|
170
|
+
|
|
171
|
+
# Apply template substitutions
|
|
172
|
+
return _apply_templates(text)
|
|
173
|
+
|
|
174
|
+
|
|
88
175
|
def _decorator_impl(
|
|
89
176
|
self,
|
|
90
177
|
agent_type: AgentType,
|
|
@@ -183,9 +270,9 @@ def _decorator_impl(
|
|
|
183
270
|
def agent(
|
|
184
271
|
self,
|
|
185
272
|
name: str = "default",
|
|
186
|
-
instruction_or_kwarg: Optional[str] = None,
|
|
273
|
+
instruction_or_kwarg: Optional[str | Path | AnyUrl] = None,
|
|
187
274
|
*,
|
|
188
|
-
instruction: str = "You are a helpful agent.",
|
|
275
|
+
instruction: str | Path | AnyUrl = "You are a helpful agent.",
|
|
189
276
|
servers: List[str] = [],
|
|
190
277
|
tools: Optional[Dict[str, List[str]]] = None,
|
|
191
278
|
resources: Optional[Dict[str, List[str]]] = None,
|
|
@@ -220,7 +307,10 @@ def agent(
|
|
|
220
307
|
Returns:
|
|
221
308
|
A decorator that registers the agent with proper type annotations
|
|
222
309
|
"""
|
|
223
|
-
|
|
310
|
+
final_instruction_raw = (
|
|
311
|
+
instruction_or_kwarg if instruction_or_kwarg is not None else instruction
|
|
312
|
+
)
|
|
313
|
+
final_instruction = _resolve_instruction(final_instruction_raw)
|
|
224
314
|
|
|
225
315
|
return _decorator_impl(
|
|
226
316
|
self,
|
|
@@ -245,9 +335,9 @@ def custom(
|
|
|
245
335
|
self,
|
|
246
336
|
cls,
|
|
247
337
|
name: str = "default",
|
|
248
|
-
instruction_or_kwarg: Optional[str] = None,
|
|
338
|
+
instruction_or_kwarg: Optional[str | Path | AnyUrl] = None,
|
|
249
339
|
*,
|
|
250
|
-
instruction: str = "You are a helpful agent.",
|
|
340
|
+
instruction: str | Path | AnyUrl = "You are a helpful agent.",
|
|
251
341
|
servers: List[str] = [],
|
|
252
342
|
tools: Optional[Dict[str, List[str]]] = None,
|
|
253
343
|
resources: Optional[Dict[str, List[str]]] = None,
|
|
@@ -277,7 +367,10 @@ def custom(
|
|
|
277
367
|
Returns:
|
|
278
368
|
A decorator that registers the agent with proper type annotations
|
|
279
369
|
"""
|
|
280
|
-
|
|
370
|
+
final_instruction_raw = (
|
|
371
|
+
instruction_or_kwarg if instruction_or_kwarg is not None else instruction
|
|
372
|
+
)
|
|
373
|
+
final_instruction = _resolve_instruction(final_instruction_raw)
|
|
281
374
|
|
|
282
375
|
return _decorator_impl(
|
|
283
376
|
self,
|
|
@@ -311,7 +404,7 @@ def orchestrator(
|
|
|
311
404
|
name: str,
|
|
312
405
|
*,
|
|
313
406
|
agents: List[str],
|
|
314
|
-
instruction: str = DEFAULT_INSTRUCTION_ORCHESTRATOR,
|
|
407
|
+
instruction: str | Path | AnyUrl = DEFAULT_INSTRUCTION_ORCHESTRATOR,
|
|
315
408
|
model: Optional[str] = None,
|
|
316
409
|
request_params: RequestParams | None = None,
|
|
317
410
|
use_history: bool = False,
|
|
@@ -341,6 +434,7 @@ def orchestrator(
|
|
|
341
434
|
"""
|
|
342
435
|
|
|
343
436
|
# Create final request params with plan_iterations
|
|
437
|
+
resolved_instruction = _resolve_instruction(instruction)
|
|
344
438
|
|
|
345
439
|
return cast(
|
|
346
440
|
"Callable[[AgentCallable[P, R]], DecoratedOrchestratorProtocol[P, R]]",
|
|
@@ -348,7 +442,7 @@ def orchestrator(
|
|
|
348
442
|
self,
|
|
349
443
|
AgentType.ORCHESTRATOR,
|
|
350
444
|
name=name,
|
|
351
|
-
instruction=
|
|
445
|
+
instruction=resolved_instruction,
|
|
352
446
|
servers=[], # Orchestrators don't connect to servers directly
|
|
353
447
|
model=model,
|
|
354
448
|
use_history=use_history,
|
|
@@ -368,7 +462,7 @@ def router(
|
|
|
368
462
|
name: str,
|
|
369
463
|
*,
|
|
370
464
|
agents: List[str],
|
|
371
|
-
instruction: Optional[str] = None,
|
|
465
|
+
instruction: Optional[str | Path | AnyUrl] = None,
|
|
372
466
|
servers: List[str] = [],
|
|
373
467
|
tools: Optional[Dict[str, List[str]]] = None,
|
|
374
468
|
resources: Optional[Dict[str, List[str]]] = None,
|
|
@@ -400,6 +494,7 @@ def router(
|
|
|
400
494
|
Returns:
|
|
401
495
|
A decorator that registers the router with proper type annotations
|
|
402
496
|
"""
|
|
497
|
+
resolved_instruction = _resolve_instruction(instruction or ROUTING_SYSTEM_INSTRUCTION)
|
|
403
498
|
|
|
404
499
|
return cast(
|
|
405
500
|
"Callable[[AgentCallable[P, R]], DecoratedRouterProtocol[P, R]]",
|
|
@@ -407,7 +502,7 @@ def router(
|
|
|
407
502
|
self,
|
|
408
503
|
AgentType.ROUTER,
|
|
409
504
|
name=name,
|
|
410
|
-
instruction=
|
|
505
|
+
instruction=resolved_instruction,
|
|
411
506
|
servers=servers,
|
|
412
507
|
model=model,
|
|
413
508
|
use_history=use_history,
|
|
@@ -429,7 +524,7 @@ def chain(
|
|
|
429
524
|
name: str,
|
|
430
525
|
*,
|
|
431
526
|
sequence: List[str],
|
|
432
|
-
instruction: Optional[str] = None,
|
|
527
|
+
instruction: Optional[str | Path | AnyUrl] = None,
|
|
433
528
|
cumulative: bool = False,
|
|
434
529
|
default: bool = False,
|
|
435
530
|
) -> Callable[[AgentCallable[P, R]], DecoratedChainProtocol[P, R]]:
|
|
@@ -456,6 +551,7 @@ def chain(
|
|
|
456
551
|
You are a chain that processes requests through a series of specialized agents in sequence.
|
|
457
552
|
Pass the output of each agent to the next agent in the chain.
|
|
458
553
|
"""
|
|
554
|
+
resolved_instruction = _resolve_instruction(instruction or default_instruction)
|
|
459
555
|
|
|
460
556
|
return cast(
|
|
461
557
|
"Callable[[AgentCallable[P, R]], DecoratedChainProtocol[P, R]]",
|
|
@@ -463,7 +559,7 @@ def chain(
|
|
|
463
559
|
self,
|
|
464
560
|
AgentType.CHAIN,
|
|
465
561
|
name=name,
|
|
466
|
-
instruction=
|
|
562
|
+
instruction=resolved_instruction,
|
|
467
563
|
sequence=sequence,
|
|
468
564
|
cumulative=cumulative,
|
|
469
565
|
default=default,
|
|
@@ -477,7 +573,7 @@ def parallel(
|
|
|
477
573
|
*,
|
|
478
574
|
fan_out: List[str],
|
|
479
575
|
fan_in: str | None = None,
|
|
480
|
-
instruction: Optional[str] = None,
|
|
576
|
+
instruction: Optional[str | Path | AnyUrl] = None,
|
|
481
577
|
include_request: bool = True,
|
|
482
578
|
default: bool = False,
|
|
483
579
|
) -> Callable[[AgentCallable[P, R]], DecoratedParallelProtocol[P, R]]:
|
|
@@ -499,6 +595,7 @@ def parallel(
|
|
|
499
595
|
You are a parallel processor that executes multiple agents simultaneously
|
|
500
596
|
and aggregates their results.
|
|
501
597
|
"""
|
|
598
|
+
resolved_instruction = _resolve_instruction(instruction or default_instruction)
|
|
502
599
|
|
|
503
600
|
return cast(
|
|
504
601
|
"Callable[[AgentCallable[P, R]], DecoratedParallelProtocol[P, R]]",
|
|
@@ -506,7 +603,7 @@ def parallel(
|
|
|
506
603
|
self,
|
|
507
604
|
AgentType.PARALLEL,
|
|
508
605
|
name=name,
|
|
509
|
-
instruction=
|
|
606
|
+
instruction=resolved_instruction,
|
|
510
607
|
servers=[], # Parallel agents don't connect to servers directly
|
|
511
608
|
fan_in=fan_in,
|
|
512
609
|
fan_out=fan_out,
|
|
@@ -522,7 +619,7 @@ def evaluator_optimizer(
|
|
|
522
619
|
*,
|
|
523
620
|
generator: str,
|
|
524
621
|
evaluator: str,
|
|
525
|
-
instruction: Optional[str] = None,
|
|
622
|
+
instruction: Optional[str | Path | AnyUrl] = None,
|
|
526
623
|
min_rating: str = "GOOD",
|
|
527
624
|
max_refinements: int = 3,
|
|
528
625
|
default: bool = False,
|
|
@@ -547,6 +644,7 @@ def evaluator_optimizer(
|
|
|
547
644
|
evaluated for quality, and then refined based on specific feedback until
|
|
548
645
|
it reaches an acceptable quality standard.
|
|
549
646
|
"""
|
|
647
|
+
resolved_instruction = _resolve_instruction(instruction or default_instruction)
|
|
550
648
|
|
|
551
649
|
return cast(
|
|
552
650
|
"Callable[[AgentCallable[P, R]], DecoratedEvaluatorOptimizerProtocol[P, R]]",
|
|
@@ -554,7 +652,7 @@ def evaluator_optimizer(
|
|
|
554
652
|
self,
|
|
555
653
|
AgentType.EVALUATOR_OPTIMIZER,
|
|
556
654
|
name=name,
|
|
557
|
-
instruction=
|
|
655
|
+
instruction=resolved_instruction,
|
|
558
656
|
servers=[], # Evaluator-optimizer doesn't connect to servers directly
|
|
559
657
|
generator=generator,
|
|
560
658
|
evaluator=evaluator,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Human input modules for forms and elicitation."""
|
|
2
|
+
|
|
3
|
+
# Export the simple form API
|
|
4
|
+
# Export field types and schema builder
|
|
5
|
+
from mcp_agent.human_input.form_fields import (
|
|
6
|
+
BooleanField,
|
|
7
|
+
EnumField,
|
|
8
|
+
FormSchema,
|
|
9
|
+
IntegerField,
|
|
10
|
+
NumberField,
|
|
11
|
+
# Field classes
|
|
12
|
+
StringField,
|
|
13
|
+
boolean,
|
|
14
|
+
choice,
|
|
15
|
+
date,
|
|
16
|
+
datetime,
|
|
17
|
+
email,
|
|
18
|
+
integer,
|
|
19
|
+
number,
|
|
20
|
+
# Convenience functions
|
|
21
|
+
string,
|
|
22
|
+
url,
|
|
23
|
+
)
|
|
24
|
+
from mcp_agent.human_input.simple_form import ask, ask_sync, form, form_sync
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
# Form functions
|
|
28
|
+
"form",
|
|
29
|
+
"form_sync",
|
|
30
|
+
"ask",
|
|
31
|
+
"ask_sync",
|
|
32
|
+
# Schema builder
|
|
33
|
+
"FormSchema",
|
|
34
|
+
# Field classes
|
|
35
|
+
"StringField",
|
|
36
|
+
"IntegerField",
|
|
37
|
+
"NumberField",
|
|
38
|
+
"BooleanField",
|
|
39
|
+
"EnumField",
|
|
40
|
+
# Field convenience functions
|
|
41
|
+
"string",
|
|
42
|
+
"email",
|
|
43
|
+
"url",
|
|
44
|
+
"date",
|
|
45
|
+
"datetime",
|
|
46
|
+
"integer",
|
|
47
|
+
"number",
|
|
48
|
+
"boolean",
|
|
49
|
+
"choice",
|
|
50
|
+
]
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""High-level field types for elicitation forms with default support."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Dict, List, Optional, Union
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class StringField:
|
|
9
|
+
"""String field with validation and default support."""
|
|
10
|
+
|
|
11
|
+
title: Optional[str] = None
|
|
12
|
+
description: Optional[str] = None
|
|
13
|
+
default: Optional[str] = None
|
|
14
|
+
min_length: Optional[int] = None
|
|
15
|
+
max_length: Optional[int] = None
|
|
16
|
+
format: Optional[str] = None # email, uri, date, date-time
|
|
17
|
+
|
|
18
|
+
def to_schema(self) -> Dict[str, Any]:
|
|
19
|
+
"""Convert to MCP elicitation schema format."""
|
|
20
|
+
schema: Dict[str, Any] = {"type": "string"}
|
|
21
|
+
|
|
22
|
+
if self.title:
|
|
23
|
+
schema["title"] = self.title
|
|
24
|
+
if self.description:
|
|
25
|
+
schema["description"] = self.description
|
|
26
|
+
if self.default is not None:
|
|
27
|
+
schema["default"] = self.default
|
|
28
|
+
if self.min_length is not None:
|
|
29
|
+
schema["minLength"] = self.min_length
|
|
30
|
+
if self.max_length is not None:
|
|
31
|
+
schema["maxLength"] = self.max_length
|
|
32
|
+
if self.format:
|
|
33
|
+
schema["format"] = self.format
|
|
34
|
+
|
|
35
|
+
return schema
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class IntegerField:
|
|
40
|
+
"""Integer field with validation and default support."""
|
|
41
|
+
|
|
42
|
+
title: Optional[str] = None
|
|
43
|
+
description: Optional[str] = None
|
|
44
|
+
default: Optional[int] = None
|
|
45
|
+
minimum: Optional[int] = None
|
|
46
|
+
maximum: Optional[int] = None
|
|
47
|
+
|
|
48
|
+
def to_schema(self) -> Dict[str, Any]:
|
|
49
|
+
"""Convert to MCP elicitation schema format."""
|
|
50
|
+
schema: Dict[str, Any] = {"type": "integer"}
|
|
51
|
+
|
|
52
|
+
if self.title:
|
|
53
|
+
schema["title"] = self.title
|
|
54
|
+
if self.description:
|
|
55
|
+
schema["description"] = self.description
|
|
56
|
+
if self.default is not None:
|
|
57
|
+
schema["default"] = self.default
|
|
58
|
+
if self.minimum is not None:
|
|
59
|
+
schema["minimum"] = self.minimum
|
|
60
|
+
if self.maximum is not None:
|
|
61
|
+
schema["maximum"] = self.maximum
|
|
62
|
+
|
|
63
|
+
return schema
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class NumberField:
|
|
68
|
+
"""Number (float) field with validation and default support."""
|
|
69
|
+
|
|
70
|
+
title: Optional[str] = None
|
|
71
|
+
description: Optional[str] = None
|
|
72
|
+
default: Optional[float] = None
|
|
73
|
+
minimum: Optional[float] = None
|
|
74
|
+
maximum: Optional[float] = None
|
|
75
|
+
|
|
76
|
+
def to_schema(self) -> Dict[str, Any]:
|
|
77
|
+
"""Convert to MCP elicitation schema format."""
|
|
78
|
+
schema: Dict[str, Any] = {"type": "number"}
|
|
79
|
+
|
|
80
|
+
if self.title:
|
|
81
|
+
schema["title"] = self.title
|
|
82
|
+
if self.description:
|
|
83
|
+
schema["description"] = self.description
|
|
84
|
+
if self.default is not None:
|
|
85
|
+
schema["default"] = self.default
|
|
86
|
+
if self.minimum is not None:
|
|
87
|
+
schema["minimum"] = self.minimum
|
|
88
|
+
if self.maximum is not None:
|
|
89
|
+
schema["maximum"] = self.maximum
|
|
90
|
+
|
|
91
|
+
return schema
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class BooleanField:
|
|
96
|
+
"""Boolean field with default support."""
|
|
97
|
+
|
|
98
|
+
title: Optional[str] = None
|
|
99
|
+
description: Optional[str] = None
|
|
100
|
+
default: Optional[bool] = None
|
|
101
|
+
|
|
102
|
+
def to_schema(self) -> Dict[str, Any]:
|
|
103
|
+
"""Convert to MCP elicitation schema format."""
|
|
104
|
+
schema: Dict[str, Any] = {"type": "boolean"}
|
|
105
|
+
|
|
106
|
+
if self.title:
|
|
107
|
+
schema["title"] = self.title
|
|
108
|
+
if self.description:
|
|
109
|
+
schema["description"] = self.description
|
|
110
|
+
if self.default is not None:
|
|
111
|
+
schema["default"] = self.default
|
|
112
|
+
|
|
113
|
+
return schema
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass
|
|
117
|
+
class EnumField:
|
|
118
|
+
"""Enum/choice field with default support."""
|
|
119
|
+
|
|
120
|
+
choices: List[str]
|
|
121
|
+
choice_names: Optional[List[str]] = None # Human-readable names
|
|
122
|
+
title: Optional[str] = None
|
|
123
|
+
description: Optional[str] = None
|
|
124
|
+
default: Optional[str] = None
|
|
125
|
+
|
|
126
|
+
def to_schema(self) -> Dict[str, Any]:
|
|
127
|
+
"""Convert to MCP elicitation schema format."""
|
|
128
|
+
schema: Dict[str, Any] = {"type": "string", "enum": self.choices}
|
|
129
|
+
|
|
130
|
+
if self.title:
|
|
131
|
+
schema["title"] = self.title
|
|
132
|
+
if self.description:
|
|
133
|
+
schema["description"] = self.description
|
|
134
|
+
if self.default is not None:
|
|
135
|
+
schema["default"] = self.default
|
|
136
|
+
if self.choice_names:
|
|
137
|
+
schema["enumNames"] = self.choice_names
|
|
138
|
+
|
|
139
|
+
return schema
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# Field type union
|
|
143
|
+
FieldType = Union[StringField, IntegerField, NumberField, BooleanField, EnumField]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class FormSchema:
|
|
147
|
+
"""High-level form schema builder."""
|
|
148
|
+
|
|
149
|
+
def __init__(self, **fields: FieldType):
|
|
150
|
+
"""Create a form schema with named fields."""
|
|
151
|
+
self.fields = fields
|
|
152
|
+
self._required_fields: List[str] = []
|
|
153
|
+
|
|
154
|
+
def required(self, *field_names: str) -> "FormSchema":
|
|
155
|
+
"""Mark fields as required."""
|
|
156
|
+
self._required_fields.extend(field_names)
|
|
157
|
+
return self
|
|
158
|
+
|
|
159
|
+
def to_schema(self) -> Dict[str, Any]:
|
|
160
|
+
"""Convert to MCP ElicitRequestedSchema format."""
|
|
161
|
+
properties = {}
|
|
162
|
+
|
|
163
|
+
for field_name, field in self.fields.items():
|
|
164
|
+
properties[field_name] = field.to_schema()
|
|
165
|
+
|
|
166
|
+
schema: Dict[str, Any] = {"type": "object", "properties": properties}
|
|
167
|
+
|
|
168
|
+
if self._required_fields:
|
|
169
|
+
schema["required"] = self._required_fields
|
|
170
|
+
|
|
171
|
+
return schema
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# Convenience functions for creating fields
|
|
175
|
+
def string(
|
|
176
|
+
title: Optional[str] = None,
|
|
177
|
+
description: Optional[str] = None,
|
|
178
|
+
default: Optional[str] = None,
|
|
179
|
+
min_length: Optional[int] = None,
|
|
180
|
+
max_length: Optional[int] = None,
|
|
181
|
+
format: Optional[str] = None,
|
|
182
|
+
) -> StringField:
|
|
183
|
+
"""Create a string field."""
|
|
184
|
+
return StringField(title, description, default, min_length, max_length, format)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def email(
|
|
188
|
+
title: Optional[str] = None, description: Optional[str] = None, default: Optional[str] = None
|
|
189
|
+
) -> StringField:
|
|
190
|
+
"""Create an email field."""
|
|
191
|
+
return StringField(title, description, default, format="email")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def url(
|
|
195
|
+
title: Optional[str] = None, description: Optional[str] = None, default: Optional[str] = None
|
|
196
|
+
) -> StringField:
|
|
197
|
+
"""Create a URL field."""
|
|
198
|
+
return StringField(title, description, default, format="uri")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def date(
|
|
202
|
+
title: Optional[str] = None, description: Optional[str] = None, default: Optional[str] = None
|
|
203
|
+
) -> StringField:
|
|
204
|
+
"""Create a date field."""
|
|
205
|
+
return StringField(title, description, default, format="date")
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def datetime(
|
|
209
|
+
title: Optional[str] = None, description: Optional[str] = None, default: Optional[str] = None
|
|
210
|
+
) -> StringField:
|
|
211
|
+
"""Create a datetime field."""
|
|
212
|
+
return StringField(title, description, default, format="date-time")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def integer(
|
|
216
|
+
title: Optional[str] = None,
|
|
217
|
+
description: Optional[str] = None,
|
|
218
|
+
default: Optional[int] = None,
|
|
219
|
+
minimum: Optional[int] = None,
|
|
220
|
+
maximum: Optional[int] = None,
|
|
221
|
+
) -> IntegerField:
|
|
222
|
+
"""Create an integer field."""
|
|
223
|
+
return IntegerField(title, description, default, minimum, maximum)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def number(
|
|
227
|
+
title: Optional[str] = None,
|
|
228
|
+
description: Optional[str] = None,
|
|
229
|
+
default: Optional[float] = None,
|
|
230
|
+
minimum: Optional[float] = None,
|
|
231
|
+
maximum: Optional[float] = None,
|
|
232
|
+
) -> NumberField:
|
|
233
|
+
"""Create a number field."""
|
|
234
|
+
return NumberField(title, description, default, minimum, maximum)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def boolean(
|
|
238
|
+
title: Optional[str] = None, description: Optional[str] = None, default: Optional[bool] = None
|
|
239
|
+
) -> BooleanField:
|
|
240
|
+
"""Create a boolean field."""
|
|
241
|
+
return BooleanField(title, description, default)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def choice(
|
|
245
|
+
choices: List[str],
|
|
246
|
+
choice_names: Optional[List[str]] = None,
|
|
247
|
+
title: Optional[str] = None,
|
|
248
|
+
description: Optional[str] = None,
|
|
249
|
+
default: Optional[str] = None,
|
|
250
|
+
) -> EnumField:
|
|
251
|
+
"""Create a choice/enum field."""
|
|
252
|
+
return EnumField(choices, choice_names, title, description, default)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Simple form API for elicitation schemas without MCP wrappers."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Any, Dict, Optional, Union
|
|
5
|
+
|
|
6
|
+
from mcp.types import ElicitRequestedSchema
|
|
7
|
+
|
|
8
|
+
from mcp_agent.human_input.elicitation_form import show_simple_elicitation_form
|
|
9
|
+
from mcp_agent.human_input.form_fields import FormSchema
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def form(
|
|
13
|
+
schema: Union[FormSchema, ElicitRequestedSchema, Dict[str, Any]],
|
|
14
|
+
message: str = "Please fill out the form",
|
|
15
|
+
title: str = "Form Input",
|
|
16
|
+
) -> Optional[Dict[str, Any]]:
|
|
17
|
+
"""
|
|
18
|
+
Simple form API that presents an elicitation form and returns results.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
schema: FormSchema, ElicitRequestedSchema, or dict schema
|
|
22
|
+
message: Message to display to the user
|
|
23
|
+
title: Title for the form (used as agent_name)
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Dict with form data if accepted, None if cancelled/declined
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
from mcp_agent.human_input.form_fields import FormSchema, string, email, integer
|
|
30
|
+
|
|
31
|
+
schema = FormSchema(
|
|
32
|
+
name=string("Name", "Your full name", min_length=2),
|
|
33
|
+
email=email("Email", "Your email address"),
|
|
34
|
+
age=integer("Age", "Your age", minimum=0, maximum=120)
|
|
35
|
+
).required("name", "email")
|
|
36
|
+
|
|
37
|
+
result = await form(schema, "Please enter your information")
|
|
38
|
+
if result:
|
|
39
|
+
print(f"Name: {result['name']}, Email: {result['email']}")
|
|
40
|
+
"""
|
|
41
|
+
# Convert schema to ElicitRequestedSchema format
|
|
42
|
+
if isinstance(schema, FormSchema):
|
|
43
|
+
elicit_schema = schema.to_schema()
|
|
44
|
+
elif isinstance(schema, dict):
|
|
45
|
+
elicit_schema = schema
|
|
46
|
+
else:
|
|
47
|
+
elicit_schema = schema
|
|
48
|
+
|
|
49
|
+
# Show the form
|
|
50
|
+
action, result = await show_simple_elicitation_form(
|
|
51
|
+
schema=elicit_schema, message=message, agent_name=title, server_name="SimpleForm"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Return results based on action
|
|
55
|
+
if action == "accept":
|
|
56
|
+
return result
|
|
57
|
+
else:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def form_sync(
|
|
62
|
+
schema: Union[FormSchema, ElicitRequestedSchema, Dict[str, Any]],
|
|
63
|
+
message: str = "Please fill out the form",
|
|
64
|
+
title: str = "Form Input",
|
|
65
|
+
) -> Optional[Dict[str, Any]]:
|
|
66
|
+
"""
|
|
67
|
+
Synchronous wrapper for the form function.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
schema: FormSchema, ElicitRequestedSchema, or dict schema
|
|
71
|
+
message: Message to display to the user
|
|
72
|
+
title: Title for the form (used as agent_name)
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Dict with form data if accepted, None if cancelled/declined
|
|
76
|
+
"""
|
|
77
|
+
return asyncio.run(form(schema, message, title))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# Convenience function with a shorter name
|
|
81
|
+
async def ask(
|
|
82
|
+
schema: Union[FormSchema, ElicitRequestedSchema, Dict[str, Any]],
|
|
83
|
+
message: str = "Please provide the requested information",
|
|
84
|
+
) -> Optional[Dict[str, Any]]:
|
|
85
|
+
"""
|
|
86
|
+
Short alias for form() function.
|
|
87
|
+
|
|
88
|
+
Example:
|
|
89
|
+
from mcp_agent.human_input.form_fields import FormSchema, string, email
|
|
90
|
+
|
|
91
|
+
schema = FormSchema(
|
|
92
|
+
name=string("Name", "Your name"),
|
|
93
|
+
email=email("Email", "Your email")
|
|
94
|
+
).required("name")
|
|
95
|
+
|
|
96
|
+
result = await ask(schema, "What's your info?")
|
|
97
|
+
"""
|
|
98
|
+
return await form(schema, message)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def ask_sync(
|
|
102
|
+
schema: Union[FormSchema, ElicitRequestedSchema, Dict[str, Any]],
|
|
103
|
+
message: str = "Please provide the requested information",
|
|
104
|
+
) -> Optional[Dict[str, Any]]:
|
|
105
|
+
"""
|
|
106
|
+
Synchronous version of ask().
|
|
107
|
+
|
|
108
|
+
Example:
|
|
109
|
+
result = ask_sync(schema, "What's your info?")
|
|
110
|
+
"""
|
|
111
|
+
return form_sync(schema, message)
|
mcp_agent/llm/augmented_llm.py
CHANGED
|
@@ -9,6 +9,7 @@ from typing import (
|
|
|
9
9
|
Tuple,
|
|
10
10
|
Type,
|
|
11
11
|
TypeVar,
|
|
12
|
+
Union,
|
|
12
13
|
cast,
|
|
13
14
|
)
|
|
14
15
|
|
|
@@ -203,7 +204,7 @@ class AugmentedLLM(ContextDependent, AugmentedLLMProtocol, Generic[MessageParamT
|
|
|
203
204
|
|
|
204
205
|
async def generate(
|
|
205
206
|
self,
|
|
206
|
-
multipart_messages: List[PromptMessageMultipart],
|
|
207
|
+
multipart_messages: List[Union[PromptMessageMultipart, PromptMessage]],
|
|
207
208
|
request_params: RequestParams | None = None,
|
|
208
209
|
) -> PromptMessageMultipart:
|
|
209
210
|
"""
|
|
@@ -212,6 +213,10 @@ class AugmentedLLM(ContextDependent, AugmentedLLMProtocol, Generic[MessageParamT
|
|
|
212
213
|
# note - check changes here are mirrored in structured(). i've thought hard about
|
|
213
214
|
# a strategy to reduce duplication etc, but aiming for simple but imperfect for the moment
|
|
214
215
|
|
|
216
|
+
# Convert PromptMessage to PromptMessageMultipart if needed
|
|
217
|
+
if multipart_messages and isinstance(multipart_messages[0], PromptMessage):
|
|
218
|
+
multipart_messages = PromptMessageMultipart.to_multipart(multipart_messages)
|
|
219
|
+
|
|
215
220
|
# TODO -- create a "fast-agent" control role rather than magic strings
|
|
216
221
|
|
|
217
222
|
if multipart_messages[-1].first_text().startswith("***SAVE_HISTORY"):
|
|
@@ -259,12 +264,16 @@ class AugmentedLLM(ContextDependent, AugmentedLLMProtocol, Generic[MessageParamT
|
|
|
259
264
|
|
|
260
265
|
async def structured(
|
|
261
266
|
self,
|
|
262
|
-
multipart_messages: List[PromptMessageMultipart],
|
|
267
|
+
multipart_messages: List[Union[PromptMessageMultipart, PromptMessage]],
|
|
263
268
|
model: Type[ModelT],
|
|
264
269
|
request_params: RequestParams | None = None,
|
|
265
270
|
) -> Tuple[ModelT | None, PromptMessageMultipart]:
|
|
266
271
|
"""Return a structured response from the LLM using the provided messages."""
|
|
267
272
|
|
|
273
|
+
# Convert PromptMessage to PromptMessageMultipart if needed
|
|
274
|
+
if multipart_messages and isinstance(multipart_messages[0], PromptMessage):
|
|
275
|
+
multipart_messages = PromptMessageMultipart.to_multipart(multipart_messages)
|
|
276
|
+
|
|
268
277
|
self._precall(multipart_messages)
|
|
269
278
|
result, assistant_response = await self._apply_prompt_provider_specific_structured(
|
|
270
279
|
multipart_messages, model, request_params
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
from typing import Any, List, Type
|
|
1
|
+
from typing import Any, List, Type, Union
|
|
2
|
+
|
|
3
|
+
from mcp.types import PromptMessage
|
|
2
4
|
|
|
3
5
|
from mcp_agent.core.exceptions import ModelConfigError
|
|
4
6
|
from mcp_agent.core.prompt import Prompt
|
|
@@ -51,7 +53,7 @@ class PlaybackLLM(PassthroughLLM):
|
|
|
51
53
|
|
|
52
54
|
async def generate(
|
|
53
55
|
self,
|
|
54
|
-
multipart_messages: List[PromptMessageMultipart],
|
|
56
|
+
multipart_messages: List[Union[PromptMessageMultipart, PromptMessage]],
|
|
55
57
|
request_params: RequestParams | None = None,
|
|
56
58
|
) -> PromptMessageMultipart:
|
|
57
59
|
"""
|
|
@@ -106,7 +108,7 @@ class PlaybackLLM(PassthroughLLM):
|
|
|
106
108
|
|
|
107
109
|
async def structured(
|
|
108
110
|
self,
|
|
109
|
-
multipart_messages: List[PromptMessageMultipart],
|
|
111
|
+
multipart_messages: List[Union[PromptMessageMultipart, PromptMessage]],
|
|
110
112
|
model: Type[ModelT],
|
|
111
113
|
request_params: RequestParams | None = None,
|
|
112
114
|
) -> tuple[ModelT | None, PromptMessageMultipart]:
|
mcp_agent/mcp/__init__.py
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP (Model Context Protocol) integration components.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from mcp.types import PromptMessage
|
|
6
|
+
|
|
7
|
+
from .helpers import (
|
|
8
|
+
get_image_data,
|
|
9
|
+
get_resource_text,
|
|
10
|
+
get_resource_uri,
|
|
11
|
+
get_text,
|
|
12
|
+
is_image_content,
|
|
13
|
+
is_resource_content,
|
|
14
|
+
is_resource_link,
|
|
15
|
+
is_text_content,
|
|
16
|
+
)
|
|
17
|
+
from .interfaces import (
|
|
18
|
+
AgentProtocol,
|
|
19
|
+
AugmentedLLMProtocol,
|
|
20
|
+
MCPConnectionManagerProtocol,
|
|
21
|
+
ModelFactoryClassProtocol,
|
|
22
|
+
ModelT,
|
|
23
|
+
ServerConnection,
|
|
24
|
+
ServerRegistryProtocol,
|
|
25
|
+
)
|
|
26
|
+
from .prompt_message_multipart import PromptMessageMultipart
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
# Types from mcp.types
|
|
30
|
+
"PromptMessage",
|
|
31
|
+
# Multipart message handling
|
|
32
|
+
"PromptMessageMultipart",
|
|
33
|
+
# Protocol interfaces
|
|
34
|
+
"AugmentedLLMProtocol",
|
|
35
|
+
"AgentProtocol",
|
|
36
|
+
"MCPConnectionManagerProtocol",
|
|
37
|
+
"ServerRegistryProtocol",
|
|
38
|
+
"ServerConnection",
|
|
39
|
+
"ModelFactoryClassProtocol",
|
|
40
|
+
"ModelT",
|
|
41
|
+
# Helper functions
|
|
42
|
+
"get_text",
|
|
43
|
+
"get_image_data",
|
|
44
|
+
"get_resource_uri",
|
|
45
|
+
"is_text_content",
|
|
46
|
+
"is_image_content",
|
|
47
|
+
"is_resource_content",
|
|
48
|
+
"is_resource_link",
|
|
49
|
+
"get_resource_text",
|
|
50
|
+
]
|
|
@@ -1,3 +1,25 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Helper modules for working with MCP content.
|
|
3
|
-
"""
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .content_helpers import (
|
|
6
|
+
get_image_data,
|
|
7
|
+
get_resource_text,
|
|
8
|
+
get_resource_uri,
|
|
9
|
+
get_text,
|
|
10
|
+
is_image_content,
|
|
11
|
+
is_resource_content,
|
|
12
|
+
is_resource_link,
|
|
13
|
+
is_text_content,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"get_text",
|
|
18
|
+
"get_image_data",
|
|
19
|
+
"get_resource_uri",
|
|
20
|
+
"is_text_content",
|
|
21
|
+
"is_image_content",
|
|
22
|
+
"is_resource_content",
|
|
23
|
+
"is_resource_link",
|
|
24
|
+
"get_resource_text",
|
|
25
|
+
]
|
mcp_agent/mcp/interfaces.py
CHANGED
|
@@ -36,6 +36,17 @@ if TYPE_CHECKING:
|
|
|
36
36
|
from mcp_agent.llm.usage_tracking import UsageAccumulator
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
__all__ = [
|
|
40
|
+
"MCPConnectionManagerProtocol",
|
|
41
|
+
"ServerRegistryProtocol",
|
|
42
|
+
"ServerConnection",
|
|
43
|
+
"AugmentedLLMProtocol",
|
|
44
|
+
"AgentProtocol",
|
|
45
|
+
"ModelFactoryClassProtocol",
|
|
46
|
+
"ModelT",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
|
|
39
50
|
@runtime_checkable
|
|
40
51
|
class MCPConnectionManagerProtocol(Protocol):
|
|
41
52
|
"""Protocol for MCPConnectionManager functionality needed by ServerRegistry."""
|
|
@@ -101,7 +112,7 @@ class AugmentedLLMProtocol(Protocol):
|
|
|
101
112
|
|
|
102
113
|
async def structured(
|
|
103
114
|
self,
|
|
104
|
-
multipart_messages: List[PromptMessageMultipart],
|
|
115
|
+
multipart_messages: List[Union[PromptMessageMultipart, PromptMessage]],
|
|
105
116
|
model: Type[ModelT],
|
|
106
117
|
request_params: RequestParams | None = None,
|
|
107
118
|
) -> Tuple[ModelT | None, PromptMessageMultipart]:
|
|
@@ -110,7 +121,7 @@ class AugmentedLLMProtocol(Protocol):
|
|
|
110
121
|
|
|
111
122
|
async def generate(
|
|
112
123
|
self,
|
|
113
|
-
multipart_messages: List[PromptMessageMultipart],
|
|
124
|
+
multipart_messages: List[Union[PromptMessageMultipart, PromptMessage]],
|
|
114
125
|
request_params: RequestParams | None = None,
|
|
115
126
|
) -> PromptMessageMultipart:
|
|
116
127
|
"""
|
mcp_agent/py.typed
ADDED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|