fast-agent-mcp 0.2.7__py3-none-any.whl → 0.2.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fast_agent_mcp-0.2.7.dist-info → fast_agent_mcp-0.2.9.dist-info}/METADATA +1 -1
- {fast_agent_mcp-0.2.7.dist-info → fast_agent_mcp-0.2.9.dist-info}/RECORD +12 -11
- mcp_agent/cli/commands/bootstrap.py +1 -0
- mcp_agent/config.py +14 -0
- mcp_agent/core/enhanced_prompt.py +28 -9
- mcp_agent/core/interactive_prompt.py +130 -26
- mcp_agent/llm/model_factory.py +5 -0
- mcp_agent/llm/providers/augmented_llm_openrouter.py +78 -0
- mcp_agent/mcp/logger_textio.py +15 -4
- {fast_agent_mcp-0.2.7.dist-info → fast_agent_mcp-0.2.9.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.7.dist-info → fast_agent_mcp-0.2.9.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.2.7.dist-info → fast_agent_mcp-0.2.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
mcp_agent/__init__.py,sha256=-AIoeL4c9UAp_P4U0z-uIWTTmQWdihOis5nbQ5L_eao,1664
|
2
2
|
mcp_agent/app.py,sha256=jBmzYM_o50g8vhlTgkkf5TGiBWNbXWViYnd0WANbpzo,10276
|
3
|
-
mcp_agent/config.py,sha256=
|
3
|
+
mcp_agent/config.py,sha256=0GVtAMSiK1oPklHlH-3rbhjPfBx18JfEAn-W-HG5x6k,12167
|
4
4
|
mcp_agent/console.py,sha256=Gjf2QLFumwG1Lav__c07X_kZxxEUSkzV-1_-YbAwcwo,813
|
5
5
|
mcp_agent/context.py,sha256=pp_F1Q1jgAxGrRccSZJutn1JUxYfVue-St3S8tUyptM,7903
|
6
6
|
mcp_agent/context_dependent.py,sha256=QXfhw3RaQCKfscEEBRGuZ3sdMWqkgShz2jJ1ivGGX1I,1455
|
@@ -22,7 +22,7 @@ mcp_agent/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
mcp_agent/cli/__main__.py,sha256=AVZ7tQFhU_sDOGuUGJq8ujgKtcxsYJBJwHbVaaiRDlI,166
|
23
23
|
mcp_agent/cli/main.py,sha256=PZdPJfsAJOm80vTu7j_XpMPhaDZOpqSe-ciU3YQsmA4,3149
|
24
24
|
mcp_agent/cli/terminal.py,sha256=GRwD-RGW7saIz2IOWZn5vD6JjiArscELBThm1GTFkuI,1065
|
25
|
-
mcp_agent/cli/commands/bootstrap.py,sha256=
|
25
|
+
mcp_agent/cli/commands/bootstrap.py,sha256=UiTt0fO_IpaHqFCKAW5tg7BIH04vPXf8xH0hJjPM52M,11602
|
26
26
|
mcp_agent/cli/commands/config.py,sha256=jU2gl4d5YESrdUboh3u6mxf7CxVT-_DT_sK8Vuh3ajw,231
|
27
27
|
mcp_agent/cli/commands/setup.py,sha256=CsmfIvKFfOhU1bOkm1cTqNseQdn3qdlfXN4BALwQ3Ik,6345
|
28
28
|
mcp_agent/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -30,11 +30,11 @@ mcp_agent/core/agent_app.py,sha256=5nQJNo8DocIRWiX4pVKAHUZF8s6HWpc-hJnfzl_1v1c,9
|
|
30
30
|
mcp_agent/core/agent_types.py,sha256=LuWslu9YI6JRnAWwh_A1ZejK72-e839wH7tf2MHxSIU,1389
|
31
31
|
mcp_agent/core/direct_decorators.py,sha256=Q6t3VpRPLCRzqJycPZIkKbbEJMVocxdScp5o2xn4gLU,14460
|
32
32
|
mcp_agent/core/direct_factory.py,sha256=hYFCucZVAQ2wrfqIe9Qameoa-cCRaQ53R97EMHvUZAM,17572
|
33
|
-
mcp_agent/core/enhanced_prompt.py,sha256=
|
33
|
+
mcp_agent/core/enhanced_prompt.py,sha256=A0FJ_-dr1RLq3uzmFSxiOBxw5synW2BhA4QntQyYVwg,18792
|
34
34
|
mcp_agent/core/error_handling.py,sha256=xoyS2kLe0eG0bj2eSJCJ2odIhGUve2SbDR7jP-A-uRw,624
|
35
35
|
mcp_agent/core/exceptions.py,sha256=ENAD_qGG67foxy6vDkIvc-lgopIUQy6O7zvNPpPXaQg,2289
|
36
36
|
mcp_agent/core/fastagent.py,sha256=T2kyq32wBJCOj13Zy1G_XJjQZb1S4HVdx3OBzmEMHBg,18644
|
37
|
-
mcp_agent/core/interactive_prompt.py,sha256=
|
37
|
+
mcp_agent/core/interactive_prompt.py,sha256=y56K2ZIvj5hZZwtEDHezJCOlduRwAcj2fc4GqhKq9ZY,23357
|
38
38
|
mcp_agent/core/mcp_content.py,sha256=2D7KHY9mG_vxoDwFLKvsPQV9VRIzHItM7V-jcEnACh8,8878
|
39
39
|
mcp_agent/core/prompt.py,sha256=qnintOUGEoDPYLI9bu9G2OlgVMCe5ZPUZilgMzydXhc,7919
|
40
40
|
mcp_agent/core/request_params.py,sha256=bEjWo86fqxdiWm2U5nPDd1uCUpcIQO9oiCinhB8lQN0,1185
|
@@ -54,7 +54,7 @@ mcp_agent/llm/augmented_llm.py,sha256=YIB3I_taoglo_vSmZLQ50cv1qCSctaQlWVwjI-7WTk
|
|
54
54
|
mcp_agent/llm/augmented_llm_passthrough.py,sha256=U0LssNWNVuZRuD9I7Wuvpo7vdDW4xtoPLirnYCgBGTY,6128
|
55
55
|
mcp_agent/llm/augmented_llm_playback.py,sha256=YVR2adzjMf9Q5WfYBytryWMRqJ87a3kNBnjxhApsMcU,3413
|
56
56
|
mcp_agent/llm/memory.py,sha256=UakoBCJBf59JBtB6uyZM0OZjlxDW_VHtSfDs08ibVEc,3312
|
57
|
-
mcp_agent/llm/model_factory.py,sha256=
|
57
|
+
mcp_agent/llm/model_factory.py,sha256=fj14NMYYg7yBxq7TsVuLIEYrK6rzPW1_p9O0Yegoq00,7844
|
58
58
|
mcp_agent/llm/prompt_utils.py,sha256=yWQHykoK13QRF7evHUKxVF0SpVLN-Bsft0Yixzvn0g0,4825
|
59
59
|
mcp_agent/llm/sampling_converter.py,sha256=C7wPBlmT0eD90XWabC22zkxsrVHKCrjwIwg6cG628cI,2926
|
60
60
|
mcp_agent/llm/sampling_format_converter.py,sha256=xGz4odHpOcP7--eFaJaFtUR8eR9jxZS7MnLH6J7n0EU,1263
|
@@ -64,6 +64,7 @@ mcp_agent/llm/providers/augmented_llm_anthropic.py,sha256=CNKpTEvWqjOteACUx_Vha0
|
|
64
64
|
mcp_agent/llm/providers/augmented_llm_deepseek.py,sha256=SdYDqZZ9hM9sBvW1FSItNn_ENEKQXGNKwVHGnjqjyAA,1927
|
65
65
|
mcp_agent/llm/providers/augmented_llm_generic.py,sha256=IIgwPYsVGwDdL2mMYsc5seY3pVFblMwmnxoI5dbxras,1524
|
66
66
|
mcp_agent/llm/providers/augmented_llm_openai.py,sha256=Wso9GVgsq8y3sqlOzTk_iQqrkCOL3LyuG07nA1PWDng,17913
|
67
|
+
mcp_agent/llm/providers/augmented_llm_openrouter.py,sha256=AajWXFIgGEDjeEx8AWCTs3mZGTPaihdsrjEUiNAJkIM,3501
|
67
68
|
mcp_agent/llm/providers/multipart_converter_anthropic.py,sha256=t5lHYGfFUacJldnrVtMNW-8gEMoto8Y7hJkDrnyZR-Y,16650
|
68
69
|
mcp_agent/llm/providers/multipart_converter_openai.py,sha256=zCj0LBgd9FDG8aL_GeTrPo2ssloYnmC_Uj3ENWVUJAg,16753
|
69
70
|
mcp_agent/llm/providers/openai_multipart.py,sha256=qKBn7d3jSabnJmVgWweVzqh8q9mBqr09fsPmP92niAQ,6899
|
@@ -81,7 +82,7 @@ mcp_agent/logging/transport.py,sha256=m8YsLLu5T8eof_ndpLQs4gHOzqqEL98xsVwBwDsBfx
|
|
81
82
|
mcp_agent/mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
82
83
|
mcp_agent/mcp/gen_client.py,sha256=fAVwFVCgSamw4PwoWOV4wrK9TABx1S_zZv8BctRyF2k,3030
|
83
84
|
mcp_agent/mcp/interfaces.py,sha256=vma7bbWbY3zp1RM6hMYxVO4aV6Vfaygm-nLwzK2jFKI,6748
|
84
|
-
mcp_agent/mcp/logger_textio.py,sha256=
|
85
|
+
mcp_agent/mcp/logger_textio.py,sha256=vljC1BtNTCxBAda9ExqNB-FwVNUZIuJT3h1nWmCjMws,3172
|
85
86
|
mcp_agent/mcp/mcp_agent_client_session.py,sha256=RMYNltc2pDIzxwEJSS5589RbvPO0KWV4Y3jSyAmhKf0,4181
|
86
87
|
mcp_agent/mcp/mcp_aggregator.py,sha256=jaWbOvb3wioECohZ47CubyxfJ5QkfNSshu1hwhZksG4,40486
|
87
88
|
mcp_agent/mcp/mcp_connection_manager.py,sha256=AMIm2FBbIk7zHInb8X-kFSQFO5TKcoi9w8WU8nx8Ig0,13834
|
@@ -133,8 +134,8 @@ mcp_agent/resources/examples/workflows/orchestrator.py,sha256=rOGilFTliWWnZ3Jx5w
|
|
133
134
|
mcp_agent/resources/examples/workflows/parallel.py,sha256=n0dFN26QvYd2wjgohcaUBflac2SzXYx-bCyxMSousJE,1884
|
134
135
|
mcp_agent/resources/examples/workflows/router.py,sha256=E4x_-c3l4YW9w1i4ARcDtkdeqIdbWEGfsMzwLYpdbVc,1677
|
135
136
|
mcp_agent/ui/console_display.py,sha256=TVGDtJ37hc6UG0ei9g7ZPZZfFNeS1MYozt-Mx8HsPCk,9752
|
136
|
-
fast_agent_mcp-0.2.
|
137
|
-
fast_agent_mcp-0.2.
|
138
|
-
fast_agent_mcp-0.2.
|
139
|
-
fast_agent_mcp-0.2.
|
140
|
-
fast_agent_mcp-0.2.
|
137
|
+
fast_agent_mcp-0.2.9.dist-info/METADATA,sha256=Jr7h2V_0O36zTw-_2XfEK2KXbDeoWR61heYQmPQOSWc,29849
|
138
|
+
fast_agent_mcp-0.2.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
139
|
+
fast_agent_mcp-0.2.9.dist-info/entry_points.txt,sha256=qPM7vwtN1_KmP3dXehxgiCxUBHtqP7yfenZigztvY-w,226
|
140
|
+
fast_agent_mcp-0.2.9.dist-info/licenses/LICENSE,sha256=cN3FxDURL9XuzE5mhK9L2paZo82LTfjwCYVT7e3j0e4,10939
|
141
|
+
fast_agent_mcp-0.2.9.dist-info/RECORD,,
|
mcp_agent/config.py
CHANGED
@@ -146,6 +146,17 @@ class GenericSettings(BaseModel):
|
|
146
146
|
base_url: str | None = None
|
147
147
|
|
148
148
|
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
149
|
+
|
150
|
+
|
151
|
+
class OpenRouterSettings(BaseModel):
|
152
|
+
"""
|
153
|
+
Settings for using OpenRouter models via its OpenAI-compatible API.
|
154
|
+
"""
|
155
|
+
api_key: str | None = None
|
156
|
+
|
157
|
+
base_url: str | None = None # Optional override, defaults handled in provider
|
158
|
+
|
159
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
149
160
|
|
150
161
|
|
151
162
|
class TemporalSettings(BaseModel):
|
@@ -262,6 +273,9 @@ class Settings(BaseSettings):
|
|
262
273
|
deepseek: DeepSeekSettings | None = None
|
263
274
|
"""Settings for using DeepSeek models in the fast-agent application"""
|
264
275
|
|
276
|
+
openrouter: OpenRouterSettings | None = None
|
277
|
+
"""Settings for using OpenRouter models in the fast-agent application"""
|
278
|
+
|
265
279
|
generic: GenericSettings | None = None
|
266
280
|
"""Settings for using Generic models in the fast-agent application"""
|
267
281
|
|
@@ -285,10 +285,17 @@ async def get_enhanced_input(
|
|
285
285
|
elif cmd == "agents":
|
286
286
|
return "LIST_AGENTS"
|
287
287
|
elif cmd == "prompts":
|
288
|
-
|
288
|
+
# Return a dictionary with select_prompt action instead of a string
|
289
|
+
# This way it will match what the command handler expects
|
290
|
+
return {"select_prompt": True, "prompt_name": None}
|
289
291
|
elif cmd == "prompt" and len(cmd_parts) > 1:
|
290
|
-
# Direct prompt selection with name
|
291
|
-
|
292
|
+
# Direct prompt selection with name or number
|
293
|
+
prompt_arg = cmd_parts[1].strip()
|
294
|
+
# Check if it's a number (use as index) or a name (use directly)
|
295
|
+
if prompt_arg.isdigit():
|
296
|
+
return {"select_prompt": True, "prompt_index": int(prompt_arg)}
|
297
|
+
else:
|
298
|
+
return f"SELECT_PROMPT:{prompt_arg}"
|
292
299
|
elif cmd == "exit":
|
293
300
|
return "EXIT"
|
294
301
|
elif cmd.lower() == "stop":
|
@@ -420,13 +427,27 @@ async def get_argument_input(
|
|
420
427
|
prompt_session.app.exit()
|
421
428
|
|
422
429
|
|
423
|
-
async def handle_special_commands(command
|
424
|
-
"""
|
430
|
+
async def handle_special_commands(command, agent_app=None):
|
431
|
+
"""
|
432
|
+
Handle special input commands.
|
433
|
+
|
434
|
+
Args:
|
435
|
+
command: The command to handle, can be string or dictionary
|
436
|
+
agent_app: Optional agent app reference
|
437
|
+
|
438
|
+
Returns:
|
439
|
+
True if command was handled, False if not, or a dict with action info
|
440
|
+
"""
|
425
441
|
# Quick guard for empty or None commands
|
426
442
|
if not command:
|
427
443
|
return False
|
444
|
+
|
445
|
+
# If command is already a dictionary, it has been pre-processed
|
446
|
+
# Just return it directly (like when /prompts converts to select_prompt dict)
|
447
|
+
if isinstance(command, dict):
|
448
|
+
return command
|
428
449
|
|
429
|
-
# Check for special commands
|
450
|
+
# Check for special string commands
|
430
451
|
if command == "HELP":
|
431
452
|
rich_print("\n[bold]Available Commands:[/bold]")
|
432
453
|
rich_print(" /help - Show this help")
|
@@ -450,7 +471,7 @@ async def handle_special_commands(command: str, agent_app=None):
|
|
450
471
|
print("\033c", end="")
|
451
472
|
return True
|
452
473
|
|
453
|
-
elif command.upper() == "EXIT":
|
474
|
+
elif isinstance(command, str) and command.upper() == "EXIT":
|
454
475
|
raise PromptExitError("User requested to exit fast-agent session")
|
455
476
|
|
456
477
|
elif command == "LIST_AGENTS":
|
@@ -462,8 +483,6 @@ async def handle_special_commands(command: str, agent_app=None):
|
|
462
483
|
rich_print("[yellow]No agents available[/yellow]")
|
463
484
|
return True
|
464
485
|
|
465
|
-
# Removed LIST_PROMPTS handling as it's now covered by SELECT_PROMPT
|
466
|
-
|
467
486
|
elif command == "SELECT_PROMPT" or (
|
468
487
|
isinstance(command, str) and command.startswith("SELECT_PROMPT:")
|
469
488
|
):
|
@@ -26,6 +26,7 @@ from mcp_agent.core.enhanced_prompt import (
|
|
26
26
|
get_selection_input,
|
27
27
|
handle_special_commands,
|
28
28
|
)
|
29
|
+
from mcp_agent.mcp.mcp_aggregator import SEP # Import SEP once at the top
|
29
30
|
from mcp_agent.progress_display import progress_display
|
30
31
|
|
31
32
|
|
@@ -96,7 +97,7 @@ class InteractivePrompt:
|
|
96
97
|
|
97
98
|
# Handle special commands - pass "True" to enable agent switching
|
98
99
|
command_result = await handle_special_commands(user_input, True)
|
99
|
-
|
100
|
+
|
100
101
|
# Check if we should switch agents
|
101
102
|
if isinstance(command_result, dict):
|
102
103
|
if "switch_agent" in command_result:
|
@@ -107,6 +108,7 @@ class InteractivePrompt:
|
|
107
108
|
else:
|
108
109
|
rich_print(f"[red]Agent '{new_agent}' not found[/red]")
|
109
110
|
continue
|
111
|
+
# Keep the existing list_prompts handler for backward compatibility
|
110
112
|
elif "list_prompts" in command_result and list_prompts_func:
|
111
113
|
# Use the list_prompts_func directly
|
112
114
|
await self._list_prompts(list_prompts_func, agent)
|
@@ -114,7 +116,29 @@ class InteractivePrompt:
|
|
114
116
|
elif "select_prompt" in command_result and (list_prompts_func and apply_prompt_func):
|
115
117
|
# Handle prompt selection, using both list_prompts and apply_prompt
|
116
118
|
prompt_name = command_result.get("prompt_name")
|
117
|
-
|
119
|
+
prompt_index = command_result.get("prompt_index")
|
120
|
+
|
121
|
+
# If a specific index was provided (from /prompt <number>)
|
122
|
+
if prompt_index is not None:
|
123
|
+
# First get a list of all prompts to look up the index
|
124
|
+
all_prompts = await self._get_all_prompts(list_prompts_func, agent)
|
125
|
+
if not all_prompts:
|
126
|
+
rich_print("[yellow]No prompts available[/yellow]")
|
127
|
+
continue
|
128
|
+
|
129
|
+
# Check if the index is valid
|
130
|
+
if 1 <= prompt_index <= len(all_prompts):
|
131
|
+
# Get the prompt at the specified index (1-based to 0-based)
|
132
|
+
selected_prompt = all_prompts[prompt_index - 1]
|
133
|
+
# Use the already created namespaced_name to ensure consistency
|
134
|
+
await self._select_prompt(list_prompts_func, apply_prompt_func, agent, selected_prompt["namespaced_name"])
|
135
|
+
else:
|
136
|
+
rich_print(f"[red]Invalid prompt number: {prompt_index}. Valid range is 1-{len(all_prompts)}[/red]")
|
137
|
+
# Show the prompt list for convenience
|
138
|
+
await self._list_prompts(list_prompts_func, agent)
|
139
|
+
else:
|
140
|
+
# Use the name-based selection
|
141
|
+
await self._select_prompt(list_prompts_func, apply_prompt_func, agent, prompt_name)
|
118
142
|
continue
|
119
143
|
|
120
144
|
# Skip further processing if command was handled
|
@@ -131,42 +155,119 @@ class InteractivePrompt:
|
|
131
155
|
|
132
156
|
return result
|
133
157
|
|
134
|
-
async def
|
158
|
+
async def _get_all_prompts(self, list_prompts_func, agent_name):
|
135
159
|
"""
|
136
|
-
|
137
|
-
|
160
|
+
Get a list of all available prompts.
|
161
|
+
|
138
162
|
Args:
|
139
163
|
list_prompts_func: Function to get available prompts
|
140
164
|
agent_name: Name of the agent
|
165
|
+
|
166
|
+
Returns:
|
167
|
+
List of prompt info dictionaries, sorted by server and name
|
141
168
|
"""
|
142
|
-
from rich import print as rich_print
|
143
|
-
|
144
169
|
try:
|
145
|
-
#
|
146
|
-
|
147
|
-
|
148
|
-
|
170
|
+
# Pass None instead of agent_name to get prompts from all servers
|
171
|
+
# the agent_name parameter should never be used as a server name
|
172
|
+
prompt_servers = await list_prompts_func(None)
|
173
|
+
all_prompts = []
|
149
174
|
|
150
175
|
# Process the returned prompt servers
|
151
176
|
if prompt_servers:
|
152
|
-
|
177
|
+
# First collect all prompts
|
153
178
|
for server_name, prompts_info in prompt_servers.items():
|
154
179
|
if prompts_info and hasattr(prompts_info, "prompts") and prompts_info.prompts:
|
155
|
-
rich_print(f"\n[bold cyan]{server_name}:[/bold cyan]")
|
156
180
|
for prompt in prompts_info.prompts:
|
157
|
-
|
158
|
-
|
181
|
+
# Use the SEP constant for proper namespacing
|
182
|
+
all_prompts.append({
|
183
|
+
"server": server_name,
|
184
|
+
"name": prompt.name,
|
185
|
+
"namespaced_name": f"{server_name}{SEP}{prompt.name}",
|
186
|
+
"description": getattr(prompt, "description", "No description"),
|
187
|
+
"arg_count": len(getattr(prompt, "arguments", [])),
|
188
|
+
"arguments": getattr(prompt, "arguments", [])
|
189
|
+
})
|
159
190
|
elif isinstance(prompts_info, list) and prompts_info:
|
160
|
-
rich_print(f"\n[bold cyan]{server_name}:[/bold cyan]")
|
161
191
|
for prompt in prompts_info:
|
162
192
|
if isinstance(prompt, dict) and "name" in prompt:
|
163
|
-
|
193
|
+
all_prompts.append({
|
194
|
+
"server": server_name,
|
195
|
+
"name": prompt["name"],
|
196
|
+
"namespaced_name": f"{server_name}{SEP}{prompt['name']}",
|
197
|
+
"description": prompt.get("description", "No description"),
|
198
|
+
"arg_count": len(prompt.get("arguments", [])),
|
199
|
+
"arguments": prompt.get("arguments", [])
|
200
|
+
})
|
164
201
|
else:
|
165
|
-
|
166
|
-
|
202
|
+
all_prompts.append({
|
203
|
+
"server": server_name,
|
204
|
+
"name": str(prompt),
|
205
|
+
"namespaced_name": f"{server_name}{SEP}{str(prompt)}",
|
206
|
+
"description": "No description",
|
207
|
+
"arg_count": 0,
|
208
|
+
"arguments": []
|
209
|
+
})
|
210
|
+
|
211
|
+
# Sort prompts by server and name for consistent ordering
|
212
|
+
all_prompts.sort(key=lambda p: (p["server"], p["name"]))
|
213
|
+
|
214
|
+
return all_prompts
|
215
|
+
|
216
|
+
except Exception as e:
|
217
|
+
import traceback
|
218
|
+
|
219
|
+
from rich import print as rich_print
|
220
|
+
rich_print(f"[red]Error getting prompts: {e}[/red]")
|
221
|
+
rich_print(f"[dim]{traceback.format_exc()}[/dim]")
|
222
|
+
return []
|
223
|
+
|
224
|
+
async def _list_prompts(self, list_prompts_func, agent_name) -> None:
|
225
|
+
"""
|
226
|
+
List available prompts for an agent.
|
227
|
+
|
228
|
+
Args:
|
229
|
+
list_prompts_func: Function to get available prompts
|
230
|
+
agent_name: Name of the agent
|
231
|
+
"""
|
232
|
+
from rich import print as rich_print
|
233
|
+
from rich.console import Console
|
234
|
+
from rich.table import Table
|
167
235
|
|
168
|
-
|
169
|
-
|
236
|
+
console = Console()
|
237
|
+
|
238
|
+
try:
|
239
|
+
# Directly call the list_prompts function for this agent
|
240
|
+
rich_print(f"\n[bold]Fetching prompts for agent [cyan]{agent_name}[/cyan]...[/bold]")
|
241
|
+
|
242
|
+
# Get all prompts using the helper function - pass None as server name
|
243
|
+
# to get prompts from all available servers
|
244
|
+
all_prompts = await self._get_all_prompts(list_prompts_func, None)
|
245
|
+
|
246
|
+
if all_prompts:
|
247
|
+
# Create a table for better display
|
248
|
+
table = Table(title="Available MCP Prompts")
|
249
|
+
table.add_column("#", justify="right", style="cyan")
|
250
|
+
table.add_column("Server", style="green")
|
251
|
+
table.add_column("Prompt Name", style="bright_blue")
|
252
|
+
table.add_column("Description")
|
253
|
+
table.add_column("Args", justify="center")
|
254
|
+
|
255
|
+
# Add prompts to table
|
256
|
+
for i, prompt in enumerate(all_prompts):
|
257
|
+
table.add_row(
|
258
|
+
str(i + 1),
|
259
|
+
prompt["server"],
|
260
|
+
prompt["name"],
|
261
|
+
prompt["description"],
|
262
|
+
str(prompt["arg_count"])
|
263
|
+
)
|
264
|
+
|
265
|
+
console.print(table)
|
266
|
+
|
267
|
+
# Add usage instructions
|
268
|
+
rich_print("\n[bold]Usage:[/bold]")
|
269
|
+
rich_print(" • Use [cyan]/prompt <number>[/cyan] to select a prompt by number")
|
270
|
+
rich_print(" • Or use [cyan]/prompts[/cyan] to open the prompt selection UI")
|
170
271
|
else:
|
171
272
|
rich_print("[yellow]No prompts available[/yellow]")
|
172
273
|
except Exception as e:
|
@@ -192,7 +293,9 @@ class InteractivePrompt:
|
|
192
293
|
try:
|
193
294
|
# Get all available prompts directly from the list_prompts function
|
194
295
|
rich_print(f"\n[bold]Fetching prompts for agent [cyan]{agent_name}[/cyan]...[/bold]")
|
195
|
-
|
296
|
+
# IMPORTANT: list_prompts_func gets MCP server prompts, not agent prompts
|
297
|
+
# So we pass None to get prompts from all servers, not using agent_name as server name
|
298
|
+
prompt_servers = await list_prompts_func(None)
|
196
299
|
|
197
300
|
if not prompt_servers:
|
198
301
|
rich_print("[yellow]No prompts available for this agent[/yellow]")
|
@@ -242,8 +345,8 @@ class InteractivePrompt:
|
|
242
345
|
else:
|
243
346
|
optional_args.append(name)
|
244
347
|
|
245
|
-
# Create namespaced version
|
246
|
-
namespaced_name = f"{server_name}
|
348
|
+
# Create namespaced version using the consistent separator
|
349
|
+
namespaced_name = f"{server_name}{SEP}{prompt_name}"
|
247
350
|
|
248
351
|
# Add to collection
|
249
352
|
all_prompts.append(
|
@@ -410,12 +513,13 @@ class InteractivePrompt:
|
|
410
513
|
arg_values[arg_name] = arg_value
|
411
514
|
|
412
515
|
# Apply the prompt
|
516
|
+
namespaced_name = selected_prompt["namespaced_name"]
|
413
517
|
rich_print(
|
414
|
-
f"\n[bold]Applying prompt [cyan]{
|
518
|
+
f"\n[bold]Applying prompt [cyan]{namespaced_name}[/cyan]...[/bold]"
|
415
519
|
)
|
416
520
|
|
417
521
|
# Call apply_prompt function with the prompt name and arguments
|
418
|
-
await apply_prompt_func(
|
522
|
+
await apply_prompt_func(namespaced_name, arg_values, agent_name)
|
419
523
|
|
420
524
|
except Exception as e:
|
421
525
|
import traceback
|
mcp_agent/llm/model_factory.py
CHANGED
@@ -11,6 +11,7 @@ from mcp_agent.llm.providers.augmented_llm_anthropic import AnthropicAugmentedLL
|
|
11
11
|
from mcp_agent.llm.providers.augmented_llm_deepseek import DeepSeekAugmentedLLM
|
12
12
|
from mcp_agent.llm.providers.augmented_llm_generic import GenericAugmentedLLM
|
13
13
|
from mcp_agent.llm.providers.augmented_llm_openai import OpenAIAugmentedLLM
|
14
|
+
from mcp_agent.llm.providers.augmented_llm_openrouter import OpenRouterAugmentedLLM
|
14
15
|
from mcp_agent.mcp.interfaces import AugmentedLLMProtocol
|
15
16
|
|
16
17
|
# from mcp_agent.workflows.llm.augmented_llm_deepseek import DeekSeekAugmentedLLM
|
@@ -23,6 +24,7 @@ LLMClass = Union[
|
|
23
24
|
Type[PassthroughLLM],
|
24
25
|
Type[PlaybackLLM],
|
25
26
|
Type[DeepSeekAugmentedLLM],
|
27
|
+
Type[OpenRouterAugmentedLLM],
|
26
28
|
]
|
27
29
|
|
28
30
|
|
@@ -34,6 +36,7 @@ class Provider(Enum):
|
|
34
36
|
FAST_AGENT = auto()
|
35
37
|
DEEPSEEK = auto()
|
36
38
|
GENERIC = auto()
|
39
|
+
OPENROUTER = auto()
|
37
40
|
|
38
41
|
|
39
42
|
class ReasoningEffort(Enum):
|
@@ -63,6 +66,7 @@ class ModelFactory:
|
|
63
66
|
"fast-agent": Provider.FAST_AGENT,
|
64
67
|
"deepseek": Provider.DEEPSEEK,
|
65
68
|
"generic": Provider.GENERIC,
|
69
|
+
"openrouter": Provider.OPENROUTER,
|
66
70
|
}
|
67
71
|
|
68
72
|
# Mapping of effort strings to enum values
|
@@ -120,6 +124,7 @@ class ModelFactory:
|
|
120
124
|
Provider.FAST_AGENT: PassthroughLLM,
|
121
125
|
Provider.DEEPSEEK: DeepSeekAugmentedLLM,
|
122
126
|
Provider.GENERIC: GenericAugmentedLLM,
|
127
|
+
Provider.OPENROUTER: OpenRouterAugmentedLLM,
|
123
128
|
}
|
124
129
|
|
125
130
|
# Mapping of special model names to their specific LLM classes
|
@@ -0,0 +1,78 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
from mcp_agent.core.exceptions import ProviderKeyError
|
4
|
+
from mcp_agent.core.request_params import RequestParams
|
5
|
+
from mcp_agent.llm.providers.augmented_llm_openai import OpenAIAugmentedLLM
|
6
|
+
|
7
|
+
OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
|
8
|
+
# No single default model for OpenRouter, users must specify full path
|
9
|
+
DEFAULT_OPENROUTER_MODEL = None
|
10
|
+
|
11
|
+
|
12
|
+
class OpenRouterAugmentedLLM(OpenAIAugmentedLLM):
|
13
|
+
"""Augmented LLM provider for OpenRouter, using an OpenAI-compatible API."""
|
14
|
+
def __init__(self, *args, **kwargs) -> None:
|
15
|
+
kwargs["provider_name"] = "OpenRouter" # Set provider name
|
16
|
+
super().__init__(*args, **kwargs)
|
17
|
+
|
18
|
+
def _initialize_default_params(self, kwargs: dict) -> RequestParams:
|
19
|
+
"""Initialize OpenRouter-specific default parameters."""
|
20
|
+
# OpenRouter model names include the provider, e.g., "google/gemini-flash-1.5"
|
21
|
+
# The model should be passed in the 'model' kwarg during factory creation.
|
22
|
+
chosen_model = kwargs.get("model", DEFAULT_OPENROUTER_MODEL)
|
23
|
+
if not chosen_model:
|
24
|
+
# Unlike Deepseek, OpenRouter *requires* a model path in the identifier.
|
25
|
+
# The factory should extract this before calling the constructor.
|
26
|
+
# We rely on the model being passed correctly via kwargs.
|
27
|
+
# If it's still None here, it indicates an issue upstream (factory or user input).
|
28
|
+
# However, the base class _get_model handles the error if model is None.
|
29
|
+
pass
|
30
|
+
|
31
|
+
|
32
|
+
return RequestParams(
|
33
|
+
model=chosen_model, # Will be validated by base class
|
34
|
+
systemPrompt=self.instruction,
|
35
|
+
parallel_tool_calls=True, # Default based on OpenAI provider
|
36
|
+
max_iterations=10, # Default based on OpenAI provider
|
37
|
+
use_history=True, # Default based on OpenAI provider
|
38
|
+
)
|
39
|
+
|
40
|
+
def _api_key(self) -> str:
|
41
|
+
"""Retrieve the OpenRouter API key from config or environment variables."""
|
42
|
+
config = self.context.config
|
43
|
+
api_key = None
|
44
|
+
|
45
|
+
# Check config file first
|
46
|
+
if config and hasattr(config, 'openrouter') and config.openrouter:
|
47
|
+
api_key = getattr(config.openrouter, 'api_key', None)
|
48
|
+
if api_key == "<your-openrouter-api-key-here>" or not api_key:
|
49
|
+
api_key = None
|
50
|
+
|
51
|
+
# Fallback to environment variable
|
52
|
+
if api_key is None:
|
53
|
+
api_key = os.getenv("OPENROUTER_API_KEY")
|
54
|
+
|
55
|
+
if not api_key:
|
56
|
+
raise ProviderKeyError(
|
57
|
+
"OpenRouter API key not configured",
|
58
|
+
"The OpenRouter API key is required but not set.\n"
|
59
|
+
"Add it to your configuration file under openrouter.api_key\n"
|
60
|
+
"Or set the OPENROUTER_API_KEY environment variable.",
|
61
|
+
)
|
62
|
+
return api_key
|
63
|
+
|
64
|
+
def _base_url(self) -> str:
|
65
|
+
"""Retrieve the OpenRouter base URL from config or use the default."""
|
66
|
+
base_url = OPENROUTER_BASE_URL # Default
|
67
|
+
config = self.context.config
|
68
|
+
|
69
|
+
# Check config file for override
|
70
|
+
if config and hasattr(config, 'openrouter') and config.openrouter:
|
71
|
+
config_base_url = getattr(config.openrouter, 'base_url', None)
|
72
|
+
if config_base_url:
|
73
|
+
base_url = config_base_url
|
74
|
+
|
75
|
+
return base_url
|
76
|
+
|
77
|
+
# Other methods like _get_model, _send_request etc., are inherited from OpenAIAugmentedLLM
|
78
|
+
# We may override them later if OpenRouter deviates significantly or offers unique features.
|
mcp_agent/mcp/logger_textio.py
CHANGED
@@ -3,7 +3,7 @@ Utilities for MCP stdio client integration with our logging system.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import io
|
6
|
-
import
|
6
|
+
import os
|
7
7
|
from typing import TextIO
|
8
8
|
|
9
9
|
from mcp_agent.logging.logger import get_logger
|
@@ -78,10 +78,21 @@ class LoggerTextIO(TextIO):
|
|
78
78
|
|
79
79
|
def fileno(self) -> int:
|
80
80
|
"""
|
81
|
-
Return a file descriptor for
|
82
|
-
|
81
|
+
Return a file descriptor for /dev/null.
|
82
|
+
This prevents output from showing on the terminal
|
83
|
+
while still allowing our write() method to capture it for logging.
|
83
84
|
"""
|
84
|
-
|
85
|
+
if not hasattr(self, '_devnull_fd'):
|
86
|
+
self._devnull_fd = os.open(os.devnull, os.O_WRONLY)
|
87
|
+
return self._devnull_fd
|
88
|
+
|
89
|
+
def __del__(self):
|
90
|
+
"""Clean up the devnull file descriptor."""
|
91
|
+
if hasattr(self, '_devnull_fd'):
|
92
|
+
try:
|
93
|
+
os.close(self._devnull_fd)
|
94
|
+
except (OSError, AttributeError):
|
95
|
+
pass
|
85
96
|
|
86
97
|
|
87
98
|
def get_stderr_handler(server_name: str) -> TextIO:
|
File without changes
|
File without changes
|
File without changes
|