universal-mcp-agents 0.1.23__py3-none-any.whl → 0.1.24rc3__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.
Files changed (35) hide show
  1. universal_mcp/agents/__init__.py +11 -2
  2. universal_mcp/agents/base.py +3 -6
  3. universal_mcp/agents/codeact0/agent.py +14 -17
  4. universal_mcp/agents/codeact0/prompts.py +9 -3
  5. universal_mcp/agents/codeact0/sandbox.py +2 -2
  6. universal_mcp/agents/codeact0/tools.py +2 -2
  7. universal_mcp/agents/codeact0/utils.py +48 -0
  8. universal_mcp/agents/codeact00/__init__.py +3 -0
  9. universal_mcp/agents/codeact00/__main__.py +26 -0
  10. universal_mcp/agents/codeact00/agent.py +578 -0
  11. universal_mcp/agents/codeact00/config.py +77 -0
  12. universal_mcp/agents/codeact00/langgraph_agent.py +14 -0
  13. universal_mcp/agents/codeact00/llm_tool.py +25 -0
  14. universal_mcp/agents/codeact00/prompts.py +364 -0
  15. universal_mcp/agents/codeact00/sandbox.py +135 -0
  16. universal_mcp/agents/codeact00/state.py +66 -0
  17. universal_mcp/agents/codeact00/tools.py +525 -0
  18. universal_mcp/agents/codeact00/utils.py +678 -0
  19. universal_mcp/agents/codeact01/__init__.py +3 -0
  20. universal_mcp/agents/codeact01/__main__.py +26 -0
  21. universal_mcp/agents/codeact01/agent.py +413 -0
  22. universal_mcp/agents/codeact01/config.py +77 -0
  23. universal_mcp/agents/codeact01/langgraph_agent.py +14 -0
  24. universal_mcp/agents/codeact01/llm_tool.py +25 -0
  25. universal_mcp/agents/codeact01/prompts.py +246 -0
  26. universal_mcp/agents/codeact01/sandbox.py +162 -0
  27. universal_mcp/agents/codeact01/state.py +58 -0
  28. universal_mcp/agents/codeact01/tools.py +648 -0
  29. universal_mcp/agents/codeact01/utils.py +552 -0
  30. universal_mcp/agents/llm.py +7 -3
  31. universal_mcp/applications/llm/app.py +66 -15
  32. {universal_mcp_agents-0.1.23.dist-info → universal_mcp_agents-0.1.24rc3.dist-info}/METADATA +1 -1
  33. universal_mcp_agents-0.1.24rc3.dist-info/RECORD +66 -0
  34. universal_mcp_agents-0.1.23.dist-info/RECORD +0 -44
  35. {universal_mcp_agents-0.1.23.dist-info → universal_mcp_agents-0.1.24rc3.dist-info}/WHEEL +0 -0
@@ -6,19 +6,26 @@ from universal_mcp.agents.builder.builder import BuilderAgent
6
6
  from universal_mcp.agents.codeact0 import CodeActPlaybookAgent
7
7
  from universal_mcp.agents.react import ReactAgent
8
8
  from universal_mcp.agents.simple import SimpleAgent
9
+ from universal_mcp.agents.codeact00 import CodeActPlaybookAgent as CodeAct00Agent
10
+ from universal_mcp.agents.codeact01 import CodeActPlaybookAgent as CodeAct01Agent
9
11
 
10
12
 
11
13
  def get_agent(
12
- agent_name: Literal["react", "simple", "builder", "bigtool", "codeact-repl"],
14
+ agent_name: Literal["react", "simple", "builder", "bigtool", "codeact-repl", "codeact-00", "codeact-01"],
13
15
  ):
16
+ print("agent_name", agent_name)
14
17
  if agent_name == "react":
15
18
  return ReactAgent
16
19
  elif agent_name == "simple":
17
20
  return SimpleAgent
18
21
  elif agent_name == "codeact-repl":
19
22
  return CodeActPlaybookAgent
23
+ elif agent_name == "codeact-00":
24
+ return CodeAct00Agent
25
+ elif agent_name == "codeact-01":
26
+ return CodeAct01Agent
20
27
  else:
21
- raise ValueError(f"Unknown agent: {agent_name}. Possible values: react, simple, codeact-repl")
28
+ raise ValueError(f"Unknown agent: {agent_name}. Possible values: react, simple, codeact-repl, codeact-00, codeact-01")
22
29
 
23
30
 
24
31
  __all__ = [
@@ -29,4 +36,6 @@ __all__ = [
29
36
  "BigToolAgent",
30
37
  "CodeActScript",
31
38
  "CodeActRepl",
39
+ "CodeAct00Agent",
40
+ "CodeAct01Agent",
32
41
  ]
@@ -67,13 +67,10 @@ class BaseAgent:
67
67
  ):
68
68
  if event == "messages" and isinstance(meta, (tuple, list)) and len(meta) == 2: # noqa: PLR2004
69
69
  payload, meta_dict = meta
70
- if meta_dict.get("tags") and "quiet" in meta_dict.get("tags"):
70
+ metadata = getattr(payload, "metadata", {}) or {}
71
+ if metadata and "quiet" in metadata.get("tags"):
71
72
  continue
72
- is_agent_builder = (
73
- isinstance(meta_dict, dict) and meta_dict.get("langgraph_node") == "agent_builder"
74
- )
75
- additional_kwargs = getattr(payload, "additional_kwargs", {}) or {}
76
- if is_agent_builder and not additional_kwargs.get("stream"):
73
+ if meta_dict.get("tags") and "quiet" in meta_dict.get("tags"):
77
74
  continue
78
75
  if isinstance(payload, AIMessageChunk):
79
76
  last_ai_chunk = payload
@@ -27,7 +27,7 @@ from universal_mcp.agents.codeact0.tools import (
27
27
  create_meta_tools,
28
28
  enter_agent_builder_mode,
29
29
  )
30
- from universal_mcp.agents.codeact0.utils import build_anthropic_cache_message, get_connected_apps_string, strip_thinking
30
+ from universal_mcp.agents.codeact0.utils import build_anthropic_cache_message, get_connected_apps_string, strip_thinking, extract_plan_parameters
31
31
  from universal_mcp.agents.llm import load_chat_model
32
32
  from universal_mcp.agents.utils import convert_tool_ids_to_dict, filter_retry_on, get_message_text
33
33
 
@@ -349,16 +349,23 @@ class CodeActPlaybookAgent(BaseAgent):
349
349
  # Use generated metadata if available
350
350
  final_name = state.get("agent_name") or function_name
351
351
  final_description = state.get("agent_description") or f"Generated agent: {function_name}"
352
+ add_context = state.get("add_context", {})
353
+ if "functions" not in add_context:
354
+ add_context["functions"] = []
355
+ add_context["functions"].append(func_code)
352
356
 
353
357
  # Save or update an Agent using the helper registry
354
358
  try:
355
359
  if not self.agent_builder_registry:
356
360
  raise ValueError("AgentBuilder registry is not configured")
357
361
 
362
+ plan_params = extract_plan_parameters(state["plan"])
363
+
358
364
  # Build instructions payload embedding the plan and function code
359
365
  instructions_payload = {
360
366
  "plan": state["plan"],
361
367
  "script": func_code,
368
+ "params": plan_params,
362
369
  }
363
370
 
364
371
  # Convert tool ids list to dict
@@ -395,22 +402,10 @@ class CodeActPlaybookAgent(BaseAgent):
395
402
  tool_call_id="exit_builder_1",
396
403
  )
397
404
  if self.eval_mode:
398
- human_msg = HumanMessage(
399
- content="Run the generated agent code and check whether it works as expected"
400
- )
401
- return Command(
402
- goto="call_model",
403
- update={
404
- "messages": [mock_assistant_message, mock_exit_tool_response, human_msg],
405
- "agent_builder_mode": "normal",
406
- },
407
- )
408
- return Command(
409
- update={
410
- "messages": [mock_assistant_message, mock_exit_tool_response],
411
- "agent_builder_mode": "normal",
412
- }
413
- )
405
+ human_msg = HumanMessage(content="Call the generated agent function (without redeclaring it) and check whether it works as expected")
406
+ return Command(goto="call_model", update={"messages": [mock_assistant_message, mock_exit_tool_response, human_msg], "agent_builder_mode": "normal", "add_context": add_context})
407
+ else:
408
+ return Command(update={"messages": [mock_assistant_message, mock_exit_tool_response], "agent_builder_mode": "normal", "add_context": add_context})
414
409
 
415
410
  writer(
416
411
  {
@@ -422,6 +417,7 @@ class CodeActPlaybookAgent(BaseAgent):
422
417
  "update": bool(self.agent),
423
418
  "name": final_name,
424
419
  "description": final_description,
420
+ "add_context": add_context,
425
421
  },
426
422
  }
427
423
  )
@@ -450,6 +446,7 @@ class CodeActPlaybookAgent(BaseAgent):
450
446
  update={
451
447
  "messages": [mock_assistant_message, mock_exit_tool_response],
452
448
  "agent_builder_mode": "normal",
449
+ "add_context": add_context,
453
450
  }
454
451
  )
455
452
 
@@ -9,7 +9,6 @@ Your job is to answer the user's question or perform the task they ask for.
9
9
  - Answer simple questions (which do not require you to write any code or access any external resources) directly. Note that any operation that involves using ONLY print functions should be answered directly in the chat. NEVER write a string or sequences of strings yourself and print it.
10
10
  - For task requiring operations or access to external resources, you should achieve the task by executing Python code snippets.
11
11
  - You have access to `execute_ipython_cell` tool that allows you to execute Python code in an IPython notebook cell.
12
- - For writing, text/document generation (like HTML/markdown document generation) or language processing tasks DO NOT answer directly. Instead you MUST use `execute_ipython_cell` tool with the llm functions provided to you for tasks like summarizing, text generation, classification, data extraction from text or unstructured data, etc. Avoid hardcoded approaches to classification, data extraction, or creative writing.
13
12
  - You also have access to two tools for finding and loading more python functions- `search_functions` and `load_functions`, which you must use for finding functions for using different external applications or additional functionality.
14
13
  - Prioritize connected applications over unconnected ones from the output of `search_functions`. However, if the user specifically asks for an application, you MUST use that irrespective of connection status.
15
14
  - When multiple relevant apps are connected, or none of the apps are connected, YOU MUST ask the user to choose the application(s). The search results may also inform you when such a case occurs, and you must stop and ask the user if multiple apps are relevant.
@@ -19,6 +18,7 @@ Your job is to answer the user's question or perform the task they ask for.
19
18
  - Structure your code into multiple small, well-defined functions within a single execution snippet. This ensures modularity and makes it easier to debug or update specific logic without rewriting or re-executing large portions of code. You can only rewrite the function/portion that you need to edit since the others are retained in context.
20
19
  - Every snippet you execute using `execute_ipython_cell` MUST follow this structure:
21
20
  - Break down logic into 3-5 small helper functions (30 lines each max)
21
+ - For definining large constants (multiline strings, dictionaries, etc) do it either at the global level or in a separate helper function responsible only for defining the object. Ensures that you do not have to rewrite large parts of the code multiple times during debugging/modifying.
22
22
  - Each helper function should do ONE thing
23
23
  - Example:
24
24
  def _helper_function_1(...):
@@ -38,7 +38,7 @@ Your job is to answer the user's question or perform the task they ask for.
38
38
  **Code Writing Rules:**
39
39
  - The code you write will be executed in a sandbox environment, and you can use the output of previous executions in your code. Variables, defined functions, imports, loaded functions are retained.
40
40
  - DO NOT use the code execution to communicate/show anything to the user. The user is not able to see the output of the code cells, it is only for your processing and actions. Similarly, you should only use print/smart_print for your own analysis, the user does not get the output.
41
- - Whenever you need to generate a large body of text, such as a document, an HTML file, or a report, use llm functions and save the output file using save/upload functions. Do not generate text yourself and do not print the entire text in order to save your memory.
41
+ - Whenever you need to generate a large body of text, such as a document, an HTML file, or a report, use llm functions (see critical section below on LLM functions) and save the output file using save/upload functions. Do not generate text yourself and do not print the entire text in order to save your memory.
42
42
  - External functions which return a dict or list[dict] are ambiguous. Therefore, you MUST explore the structure of the returned data using `smart_print()` statements before using it, printing keys and values. `smart_print` truncates long strings from data, preventing huge output logs.
43
43
  - When an operation involves running a fixed set of steps on a list of items, run one run correctly and then use a for loop to run the steps on each item in the list.
44
44
  - You can only import libraries that come pre-installed with Python. However, do consider searching for external functions first, using the search and load tools to access them in the code.
@@ -50,12 +50,17 @@ Your job is to answer the user's question or perform the task they ask for.
50
50
  - Final output summarization after analysis
51
51
  - Anything that's just formatted print statements
52
52
 
53
+ **Critical:LLM Function Usage Rules:**
54
+ - For text creation or document generation (e.g., HTML, Markdown, textual content for document/email), do not respond directly; you must use execute_ipython_cell with llm__generate_text.
55
+ - For any data extraction, text analysis, or classification task, always use the LLM functions (llm__extract_data, llm__classify_data, or llm__call_llm)-never rely on regex, manual parsing, or heuristic methods.
56
+ - Use llm__call_llm only as a fallback when the other functions don't match the task exactly.
57
+
53
58
  **Final Output Requirements:**
54
59
  - Once you have all the information about the task, return the text directly to user in markdown format. Do NOT call `execute_ipython_cell` or any LLM tools again just for summarization. Do NOT use llm__generate_text for this purpose.
55
60
  - Always respond in github flavoured markdown format.
56
61
  - For charts and diagrams, use mermaid chart in markdown directly.
57
62
  - Your final response should contain the complete answer to the user's request in a clear, well-formatted manner that directly addresses what they asked for.
58
- - For file types like images, audio, documents, etc., you must use the `upload_file` tool to upload the file to the server and render the link in the markdown response.
63
+ - For file types like images, audio, documents, etc., you must use the `upload_file` tool to upload the file to the server and render the link/path in the markdown response.
59
64
  """
60
65
 
61
66
  AGENT_BUILDER_PLANNING_PROMPT = """TASK: Analyze the conversation history and code execution to create a step-by-step non-technical plan for a reusable function.
@@ -115,6 +120,7 @@ Rules-
115
120
  2) One top-level orchestrator function that calls the step functions in sequence to achieve the plan objectives.
116
121
  - The orchestrator function's parameters **must exactly match the external variables** in the agent plan (the ones marked with backticks `` `variable_name` ``). Provide defaults exactly as specified in the plan when present. Variables in italics (i.e. enclosed in *...*) are internal and must not be orchestrator parameters.
117
122
  - The orchestrator function MUST be declared with `def` or `async def` and be directly runnable with a single Python command (e.g., `image_generator(...)`). If it is async, assume the caller will `await` it.
123
+ - NEVER use asyncio or asyncio.run(). The code is executed in a ipython environment, so using await is enough.
118
124
  - Step functions should accept only the inputs they need, return explicit outputs, and pass intermediate results forward via return values—not globals.
119
125
  - Name functions in snake_case derived from their purpose/step. Use keyword arguments in calls; avoid positional-only calls.
120
126
  - Keep the code self-contained and executable. Put imports at the top of the code. Do not nest functions unless strictly necessary.
@@ -125,8 +125,8 @@ async def handle_execute_ipython_cell(
125
125
 
126
126
  Returns (output, new_context, new_add_context).
127
127
  """
128
- add_context = inject_context(effective_previous_add_context, tools_context)
129
- context = {**effective_existing_context, **add_context}
128
+ context = {**tools_context, **effective_existing_context}
129
+ context = inject_context(effective_previous_add_context, context)
130
130
  if inspect.iscoroutinefunction(eval_fn):
131
131
  output, new_context, new_add_context = await eval_fn(code, context, effective_previous_add_context, 180)
132
132
  else:
@@ -420,7 +420,7 @@ def create_meta_tools(tool_registry: AgentrRegistry) -> dict[str, Any]:
420
420
 
421
421
  def upload_file(file_name: str, mime_type: str, base64_data: str) -> dict:
422
422
  """
423
- Uploads a file to the server via the AgentrClient.
423
+ Uploads a file to the server.
424
424
 
425
425
  Args:
426
426
  file_name (str): The name of the file to upload.
@@ -503,7 +503,7 @@ async def get_valid_tools(tool_ids: list[str], registry: AgentrRegistry) -> tupl
503
503
  start = text.find(":") + 1
504
504
  end = text.find(". R", start)
505
505
  url = text[start:end].strip()
506
- markdown_link = f"![Connect to {app.capitalize()}]({url})"
506
+ markdown_link = f"[Connect to {app.capitalize()}]({url})"
507
507
  unconnected_links.append(markdown_link)
508
508
  for tool_id, tool_name in tool_entries:
509
509
  if tool_name in available:
@@ -29,6 +29,7 @@ def build_anthropic_cache_message(text: str, role: str = "system", ttl: str = "1
29
29
  }
30
30
  ]
31
31
 
32
+
32
33
  def strip_thinking(messages: list[BaseMessage]):
33
34
  """Remove Anthropic 'thinking' segments from the most recent AIMessage in-place.
34
35
 
@@ -502,3 +503,50 @@ async def get_connected_apps_string(registry) -> str:
502
503
  return "\n".join(apps_list)
503
504
  except Exception:
504
505
  return "Unable to retrieve connected applications."
506
+
507
+ def extract_plan_parameters(plan_steps: list[str]) -> list[dict[str, Any]]:
508
+ """
509
+ Extracts parameters from plan steps and formats them into a list of OpenAPI-like parameter objects.
510
+
511
+ Parses parameters enclosed in backticks, identifying their name, if they are required, and any default values.
512
+ e.g., `variable` -> {"name": "variable", "required": True}
513
+ e.g., `variable(default = 'value')` -> {"name": "variable", "required": False, "default": "value"}
514
+ """
515
+ parameters_map: dict[str, Any] = {}
516
+ # Regex to find anything inside backticks
517
+ outer_pattern = re.compile(r"`([^`]+)`")
518
+ # Regex to parse parameters with default values
519
+ inner_pattern = re.compile(r"^\s*(\w+)\s*\(\s*default\s*=\s*(.+)\s*\)\s*$")
520
+
521
+ for step in plan_steps:
522
+ matches = outer_pattern.findall(step)
523
+ for match in matches:
524
+ param_str = match.strip()
525
+ inner_match = inner_pattern.match(param_str)
526
+
527
+ if inner_match:
528
+ # Parameter with a default value
529
+ name, default_val_str = inner_match.groups()
530
+ default_value: Any
531
+ try:
532
+ # Safely evaluate the default value (e.g., 'string', 123, True)
533
+ default_value = ast.literal_eval(default_val_str)
534
+ except (ValueError, SyntaxError):
535
+ # If it's not a valid literal, treat it as a string
536
+ default_value = default_val_str
537
+ parameters_map[name] = {"required": False, "default": default_value}
538
+ else:
539
+ # Required parameter (no default value)
540
+ name = param_str
541
+ # Only set as required if it hasn't been defined with a default already
542
+ if name not in parameters_map:
543
+ parameters_map[name] = {"required": True}
544
+
545
+ # Convert the map to the final list format
546
+ final_parameters = []
547
+ for name, details in sorted(parameters_map.items()):
548
+ param_obj = {"name": name}
549
+ param_obj.update(details)
550
+ final_parameters.append(param_obj)
551
+
552
+ return final_parameters
@@ -0,0 +1,3 @@
1
+ from .agent import CodeActPlaybookAgent
2
+
3
+ __all__ = ["CodeActPlaybookAgent"]
@@ -0,0 +1,26 @@
1
+ import asyncio
2
+
3
+ from langgraph.checkpoint.memory import MemorySaver
4
+ from rich import print
5
+ from universal_mcp.agentr.registry import AgentrRegistry
6
+
7
+ from universal_mcp.agents.codeact00.agent import CodeActPlaybookAgent
8
+ from universal_mcp.agents.utils import messages_to_list
9
+
10
+
11
+ async def main():
12
+ memory = MemorySaver()
13
+ agent = CodeActPlaybookAgent(
14
+ name="CodeAct Agent",
15
+ instructions="Be very concise in your answers.",
16
+ model="azure/gpt-4.1",
17
+ registry=AgentrRegistry(),
18
+ memory=memory,
19
+ )
20
+ print("Starting agent...")
21
+ result = await agent.invoke(user_input="Check my google calendar and show my todays agenda")
22
+ print(messages_to_list(result["messages"]))
23
+
24
+
25
+ if __name__ == "__main__":
26
+ asyncio.run(main())