universal-mcp-agents 0.1.15__py3-none-any.whl → 0.1.17__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 universal-mcp-agents might be problematic. Click here for more details.

@@ -5,7 +5,7 @@ from typing import Any, Literal, cast
5
5
  from langchain.chat_models import init_chat_model
6
6
  from langchain_openai import AzureChatOpenAI
7
7
 
8
- from universal_mcp.agents.codeact0.utils import get_message_text
8
+ from universal_mcp.agents.codeact0.utils import get_message_text, light_copy
9
9
 
10
10
  MAX_RETRIES = 3
11
11
 
@@ -27,6 +27,7 @@ def smart_print(data: Any) -> None:
27
27
  Args:
28
28
  data: Either a dictionary with string keys, or a list of such dictionaries
29
29
  """
30
+ print(light_copy(data)) # noqa
30
31
 
31
32
 
32
33
  def creative_writer(
@@ -25,7 +25,7 @@ from universal_mcp.agents.codeact0.state import CodeActState
25
25
  from universal_mcp.agents.codeact0.tools import create_meta_tools, enter_playbook_mode, exit_playbook_mode, get_valid_tools
26
26
  from universal_mcp.agents.codeact0.utils import inject_context, smart_truncate
27
27
  from universal_mcp.agents.llm import load_chat_model
28
- from universal_mcp.agents.utils import filter_retry_on, get_message_text
28
+ from universal_mcp.agents.utils import filter_retry_on, get_message_text, convert_tool_ids_to_dict
29
29
 
30
30
  PLAYBOOK_PLANNING_PROMPT = """Now, you are tasked with creating a reusable playbook from the user's previous workflow.
31
31
 
@@ -69,6 +69,7 @@ class CodeActPlaybookAgent(BaseAgent):
69
69
  memory: BaseCheckpointSaver | None = None,
70
70
  tools: ToolConfig | None = None,
71
71
  registry: ToolRegistry | None = None,
72
+ playbook_registry: object | None = None,
72
73
  sandbox_timeout: int = 20,
73
74
  **kwargs,
74
75
  ):
@@ -82,33 +83,33 @@ class CodeActPlaybookAgent(BaseAgent):
82
83
  self.model_instance = load_chat_model(model, thinking=True)
83
84
  self.tools_config = tools or []
84
85
  self.registry = registry
86
+ self.playbook_registry = playbook_registry
85
87
  self.eval_fn = eval_unsafe
86
88
  self.sandbox_timeout = sandbox_timeout
87
89
  self.processed_tools: list[StructuredTool | Callable] = []
88
90
 
89
91
  async def _build_graph(self):
90
- self.exported_tools = []
91
- if self.tools_config:
92
- # Convert dict format to list format if needed
93
- if isinstance(self.tools_config, dict):
94
- self.tools_config = [
95
- f"{provider}__{tool}"
96
- for provider, tools in self.tools_config.items()
97
- for tool in tools
98
- ]
99
- if not self.registry:
100
- raise ValueError("Tools are configured but no registry is provided")
101
- # Langchain tools are fine
102
- self.exported_tools = await self.registry.export_tools(self.tools_config, ToolFormat.LANGCHAIN)
103
92
  meta_tools = create_meta_tools(self.registry)
104
- await self.registry.export_tools(["exa__search_with_filters"], ToolFormat.LANGCHAIN)
105
93
  additional_tools = [smart_print, data_extractor, ai_classify, call_llm, meta_tools["web_search"]]
106
94
  self.additional_tools = [t if isinstance(t, StructuredTool) else create_tool(t) for t in additional_tools]
107
- self.final_instructions, self.tools_context = create_default_prompt(
108
- self.exported_tools, self.additional_tools, self.instructions
109
- )
110
-
111
- def call_model(state: CodeActState) -> Command[Literal["sandbox", "execute_tools"]]:
95
+ async def call_model(state: CodeActState) -> Command[Literal["sandbox", "execute_tools"]]:
96
+ self.exported_tools = []
97
+ if self.tools_config:
98
+ # Convert dict format to list format if needed
99
+ if isinstance(self.tools_config, dict):
100
+ self.tools_config = [
101
+ f"{provider}__{tool}"
102
+ for provider, tools in self.tools_config.items()
103
+ for tool in tools
104
+ ]
105
+ if not self.registry:
106
+ raise ValueError("Tools are configured but no registry is provided")
107
+ # Langchain tools are fine
108
+ self.tools_config.extend(state.get('selected_tool_ids',[]))
109
+ self.exported_tools = await self.registry.export_tools(self.tools_config, ToolFormat.LANGCHAIN)
110
+ self.final_instructions, self.tools_context = create_default_prompt(
111
+ self.exported_tools, self.additional_tools, self.instructions
112
+ )
112
113
  messages = [{"role": "system", "content": self.final_instructions}] + state["messages"]
113
114
 
114
115
  # Run the model and potentially loop for reflection
@@ -262,16 +263,16 @@ class CodeActPlaybookAgent(BaseAgent):
262
263
  # Extract plan from response text between triple backticks
263
264
  plan_match = re.search(r'```(.*?)```', response_text, re.DOTALL)
264
265
  if plan_match:
265
- self.plan = plan_match.group(1).strip()
266
+ plan = plan_match.group(1).strip()
266
267
  else:
267
- self.plan = response_text.strip()
268
- return Command(update={"messages": [response], "playbook_mode": "confirming"})
268
+ plan = response_text.strip()
269
+ return Command(update={"messages": [response], "playbook_mode": "confirming", "plan": plan})
269
270
 
270
271
 
271
272
  elif playbook_mode == "confirming":
272
273
  confirmation_instructions = self.instructions + PLAYBOOK_CONFIRMING_PROMPT
273
274
  messages = [{"role": "system", "content": confirmation_instructions}] + state["messages"]
274
- response = self.model_instance.invoke(messages)
275
+ response = self.model_instance.invoke(messages, stream=False)
275
276
  response = get_message_text(response)
276
277
  if "true" in response.lower():
277
278
  return Command(goto="playbook", update={"playbook_mode": "generating"})
@@ -296,24 +297,34 @@ class CodeActPlaybookAgent(BaseAgent):
296
297
  else:
297
298
  function_name = "generated_playbook"
298
299
 
300
+ # Save or update an Agent using the helper registry
301
+ saved_note = ""
299
302
  try:
300
- current_path = Path(__file__).resolve()
301
- repo_root = None
302
- for ancestor in current_path.parents:
303
- if ancestor.name == "src":
304
- repo_root = ancestor.parent
305
- break
306
- if repo_root is None:
307
- repo_root = current_path.parents[-1]
308
-
309
- playbooks_dir = repo_root / "playbooks"
310
- playbooks_dir.mkdir(parents=True, exist_ok=True)
311
-
312
- file_path = playbooks_dir / f"{function_name}.py"
313
- file_path.write_text(func_code, encoding="utf-8")
314
- saved_note = f"Playbook function saved to: {file_path} ```{func_code}```"
303
+ if not self.playbook_registry:
304
+ raise ValueError("Playbook registry is not configured")
305
+
306
+ # Build instructions payload embedding the plan and function code
307
+ instructions_payload = {
308
+ "playbookPlan": state["plan"],
309
+ "playbookScript": {
310
+ "name": function_name,
311
+ "code": func_code,
312
+ },
313
+ }
314
+
315
+ # Convert tool ids list to dict
316
+ tool_dict = convert_tool_ids_to_dict(state["selected_tool_ids"])
317
+
318
+ res = self.playbook_registry.create_agent(
319
+ name=function_name,
320
+ description=f"Generated playbook: {function_name}",
321
+ instructions=instructions_payload,
322
+ tools=tool_dict,
323
+ visibility="private",
324
+ )
325
+ saved_note = f"Successfully created your playbook! Check it out here: [View Playbook](https://wingmen.info/agents/{res.id})"
315
326
  except Exception as e:
316
- saved_note = f"Failed to save playbook function {function_name}: {e}"
327
+ saved_note = f"Failed to save generated playbook as Agent '{function_name}': {e}"
317
328
 
318
329
  # Mock tool call for exit_playbook_mode (for testing/demonstration)
319
330
  mock_exit_tool_call = {
@@ -322,7 +333,7 @@ class CodeActPlaybookAgent(BaseAgent):
322
333
  "id": "mock_exit_playbook_123"
323
334
  }
324
335
  mock_assistant_message = AIMessage(
325
- content="", # Can be empty or a brief message
336
+ content=saved_note,
326
337
  tool_calls=[mock_exit_tool_call]
327
338
  )
328
339
 
@@ -34,3 +34,5 @@ class CodeActState(AgentState):
34
34
  """State for the playbook agent."""
35
35
  selected_tool_ids: Annotated[list[str], _enqueue]
36
36
  """Queue for tools exported from registry"""
37
+ plan: str | None
38
+ """Plan for the playbook agent."""
@@ -4,6 +4,7 @@ from typing import Any
4
4
 
5
5
  from langchain_core.tools import tool
6
6
  from universal_mcp.tools.registry import ToolRegistry
7
+ from universal_mcp.types import ToolFormat
7
8
 
8
9
  MAX_LENGHT=100
9
10
 
@@ -30,8 +31,8 @@ def create_meta_tools(tool_registry: ToolRegistry) -> dict[str, Any]:
30
31
  connections = await tool_registry.list_connected_apps()
31
32
  connected_apps = {connection["app_id"] for connection in connections}
32
33
 
33
- # Use defaultdict to avoid key existence checks
34
- app_tools = defaultdict(list)
34
+ app_tools = defaultdict(set)
35
+ MAX_LENGTH = 20
35
36
 
36
37
  # Process all queries concurrently
37
38
  search_tasks = []
@@ -40,20 +41,24 @@ def create_meta_tools(tool_registry: ToolRegistry) -> dict[str, Any]:
40
41
 
41
42
  query_results = await asyncio.gather(*search_tasks)
42
43
 
43
- # Aggregate results with limit per app
44
+ # Aggregate results with limit per app and automatic deduplication
44
45
  for tools_list in query_results:
45
46
  for tool in tools_list:
46
47
  app = tool["id"].split("__")[0]
47
- if len(app_tools[app]) < MAX_LENGHT:
48
+ tool_id = tool["id"]
49
+
50
+ # Check if within limit and add to set (automatically deduplicates)
51
+ if len(app_tools[app]) < MAX_LENGTH:
48
52
  cleaned_desc = tool["description"].split("Context:")[0].strip()
49
- app_tools[app].append(f"{tool['id']}: {cleaned_desc}")
53
+ app_tools[app].add(f"{tool_id}: {cleaned_desc}")
50
54
 
51
55
  # Build result string efficiently
52
56
  result_parts = []
53
57
  for app, tools in app_tools.items():
54
58
  app_status = "connected" if app in connected_apps else "NOT connected"
55
59
  result_parts.append(f"Tools from {app} (status: {app_status} by user):")
56
- for tool in tools:
60
+ # Convert set to sorted list for consistent output
61
+ for tool in sorted(tools):
57
62
  result_parts.append(f" - {tool}")
58
63
  result_parts.append("") # Empty line between apps
59
64
 
@@ -116,6 +121,7 @@ def create_meta_tools(tool_registry: ToolRegistry) -> dict[str, Any]:
116
121
  Example:
117
122
  results = await web_search(query="python programming")
118
123
  """
124
+ await tool_registry.export_tools(["exa__search_with_filters"], ToolFormat.LANGCHAIN)
119
125
  response = await tool_registry.call_tool(
120
126
  "exa__search_with_filters", {"query": query, "contents": {"summary": True}}
121
127
  )
@@ -6,7 +6,7 @@ from typing import Any
6
6
 
7
7
  from langchain_core.messages import BaseMessage
8
8
 
9
- MAX_CHARS = 300
9
+ MAX_CHARS = 700
10
10
 
11
11
 
12
12
  def light_copy(data):
@@ -214,3 +214,21 @@ def filter_retry_on(exc: Exception) -> bool:
214
214
 
215
215
  # Default: do not retry unknown exceptions
216
216
  return False
217
+
218
+
219
+ def convert_tool_ids_to_dict(tool_ids: list[str]) -> dict[str, list[str]]:
220
+ """Convert list of tool ids like 'provider__tool' into a provider->tools dict.
221
+
222
+ Any ids without the expected delimiter are ignored.
223
+ """
224
+ provider_to_tools: dict[str, list[str]] = {}
225
+ for tool_id in tool_ids or []:
226
+ if "__" not in tool_id:
227
+ continue
228
+ provider, tool = tool_id.split("__", 1)
229
+ if not provider or not tool:
230
+ continue
231
+ if provider not in provider_to_tools:
232
+ provider_to_tools[provider] = []
233
+ provider_to_tools[provider].append(tool)
234
+ return provider_to_tools
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp-agents
3
- Version: 0.1.15
3
+ Version: 0.1.17
4
4
  Summary: Add your description here
5
5
  Project-URL: Homepage, https://github.com/universal-mcp/applications
6
6
  Project-URL: Repository, https://github.com/universal-mcp/applications
@@ -5,7 +5,7 @@ universal_mcp/agents/hil.py,sha256=_5PCK6q0goGm8qylJq44aSp2MadP-yCPvhOJYKqWLMo,3
5
5
  universal_mcp/agents/llm.py,sha256=hVRwjZs3MHl5_3BWedmurs2Jt1oZDfFX0Zj9F8KH7fk,1787
6
6
  universal_mcp/agents/react.py,sha256=8XQvJ0HLVgc-K0qn9Ml48WGcgUGuIKtL67HatlT6Da0,3334
7
7
  universal_mcp/agents/simple.py,sha256=NSATg5TWzsRNS7V3LFiDG28WSOCIwCdcC1g7NRwg2nM,2095
8
- universal_mcp/agents/utils.py,sha256=lGeDam3Efcrxv6dve31LQonBtwkSpNmBuoNXap_TqaQ,7128
8
+ universal_mcp/agents/utils.py,sha256=P6W9k6XAOBp6tdjC2VTP4tE0B2M4-b1EDmr-ylJ47Pw,7765
9
9
  universal_mcp/agents/bigtool/__init__.py,sha256=mZG8dsaCVyKlm82otxtiTA225GIFLUCUUYPEIPF24uw,2299
10
10
  universal_mcp/agents/bigtool/__main__.py,sha256=0i-fbd2yQ90qa8n2nM3luqoJVN9Reh5HZXR5oK7SAck,445
11
11
  universal_mcp/agents/bigtool/agent.py,sha256=mtCDNN8WjE2hjJjooDqusmbferKBHeJMHrhXUPUWaVc,252
@@ -32,19 +32,19 @@ universal_mcp/agents/codeact0/__main__.py,sha256=V2wLWW9ym3rtiSvPEs-N0Mki7G5dYHz
32
32
  universal_mcp/agents/codeact0/agent.py,sha256=9BInAQr3csES-XHSscmeJlYJ3-wQUHPvLOf-6wFILUU,6695
33
33
  universal_mcp/agents/codeact0/config.py,sha256=H-1woj_nhSDwf15F63WYn723y4qlRefXzGxuH81uYF0,2215
34
34
  universal_mcp/agents/codeact0/langgraph_agent.py,sha256=ehjMV_Z1118pCFWB_Sa5H7XnUp0udsbUHjfjXjhIQM8,435
35
- universal_mcp/agents/codeact0/llm_tool.py,sha256=I7QIlgZZbzBdxHuNUYODA28Z7xqWgYz5v5qWSWqB0rE,13781
36
- universal_mcp/agents/codeact0/playbook_agent.py,sha256=oj8zP-c8rObzjAlS-lkP-pl3xleIr0fCJ_ENtn-C_OM,17435
35
+ universal_mcp/agents/codeact0/llm_tool.py,sha256=fydA4BbSnhG3OLMR_VEtJxTPTmRK_mMvXGsUCO-5Mig,13829
36
+ universal_mcp/agents/codeact0/playbook_agent.py,sha256=YNuWQ_xK9KlOtb4kkHkxLl3CEJfmM3sUf8dwaSPyYcg,18089
37
37
  universal_mcp/agents/codeact0/prompts.py,sha256=j8HxA3Rp-EZsms9qMBcRmFrUjeySrG1IWjqrNFXZZn8,9457
38
38
  universal_mcp/agents/codeact0/sandbox.py,sha256=zMgHrWnQYkSkJb2MzfXvT3euCc4hvqzBE_EbX2_iLxA,3142
39
- universal_mcp/agents/codeact0/state.py,sha256=Qcr1whST3J8v7w0opnKSfOG9u5gRtxAzPs2NFhaAhJE,1199
40
- universal_mcp/agents/codeact0/tools.py,sha256=emfBLA3eChQ5B1wirOWf5RWHMy3OIRDQYnN5h5OPqFk,7401
41
- universal_mcp/agents/codeact0/utils.py,sha256=s1SfVrC1_UePxYSIL9zt-cG0xhwzFPuViyLl2Xj-45c,15943
39
+ universal_mcp/agents/codeact0/state.py,sha256=Y-Rzn_S7--aXH18KPvyhqDqOOB-miu1lsAmLgmMlaAg,1259
40
+ universal_mcp/agents/codeact0/tools.py,sha256=Adh9Go87JnIk5U_rtBOHglFRPowk2JmRK9bumR4zJoo,7751
41
+ universal_mcp/agents/codeact0/utils.py,sha256=ACncBI9-JJ4Qe_4VHWnSziY3P2nE6Vb9pj71NPeVZ0o,15943
42
42
  universal_mcp/agents/shared/__main__.py,sha256=XxH5qGDpgFWfq7fwQfgKULXGiUgeTp_YKfcxftuVZq8,1452
43
43
  universal_mcp/agents/shared/prompts.py,sha256=yjP3zbbuKi87qCj21qwTTicz8TqtkKgnyGSeEjMu3ho,3761
44
44
  universal_mcp/agents/shared/tool_node.py,sha256=DC9F-Ri28Pam0u3sXWNODVgmj9PtAEUb5qP1qOoGgfs,9169
45
45
  universal_mcp/applications/llm/__init__.py,sha256=xnpxq4Wl_pevvwtSUtEwcty8_d61ywO1V2YnEXyCREY,46
46
46
  universal_mcp/applications/llm/app.py,sha256=iNLU6z2LRZc01GfSKvV0vNzT1LhKAjq_UrSJEmjthjw,6032
47
47
  universal_mcp/applications/ui/app.py,sha256=c7OkZsO2fRtndgAzAQbKu-1xXRuRp9Kjgml57YD2NR4,9459
48
- universal_mcp_agents-0.1.15.dist-info/METADATA,sha256=8FNfgWbbFHVStTqIwzHZzohMEvvD27IuPvIUWRjea80,878
49
- universal_mcp_agents-0.1.15.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
50
- universal_mcp_agents-0.1.15.dist-info/RECORD,,
48
+ universal_mcp_agents-0.1.17.dist-info/METADATA,sha256=GFVLLOc3wPsDCvYnLV7w1klLJJIITEax5ipX_MScI98,878
49
+ universal_mcp_agents-0.1.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
50
+ universal_mcp_agents-0.1.17.dist-info/RECORD,,