universal-mcp-agents 0.1.21__py3-none-any.whl → 0.1.23rc1__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.
- universal_mcp/agents/codeact0/agent.py +72 -20
- universal_mcp/agents/codeact0/prompts.py +43 -5
- universal_mcp/agents/codeact0/state.py +9 -0
- universal_mcp/agents/codeact0/tools.py +15 -22
- universal_mcp/agents/codeact0/utils.py +48 -11
- {universal_mcp_agents-0.1.21.dist-info → universal_mcp_agents-0.1.23rc1.dist-info}/METADATA +1 -1
- {universal_mcp_agents-0.1.21.dist-info → universal_mcp_agents-0.1.23rc1.dist-info}/RECORD +8 -8
- {universal_mcp_agents-0.1.21.dist-info → universal_mcp_agents-0.1.23rc1.dist-info}/WHEEL +0 -0
|
@@ -16,10 +16,11 @@ from universal_mcp.agents.codeact0.llm_tool import smart_print
|
|
|
16
16
|
from universal_mcp.agents.codeact0.prompts import (
|
|
17
17
|
PLAYBOOK_GENERATING_PROMPT,
|
|
18
18
|
PLAYBOOK_PLANNING_PROMPT,
|
|
19
|
+
PLAYBOOK_META_PROMPT,
|
|
19
20
|
create_default_prompt,
|
|
20
21
|
)
|
|
21
22
|
from universal_mcp.agents.codeact0.sandbox import eval_unsafe, execute_ipython_cell, handle_execute_ipython_cell
|
|
22
|
-
from universal_mcp.agents.codeact0.state import CodeActState, PlaybookCode, PlaybookPlan
|
|
23
|
+
from universal_mcp.agents.codeact0.state import CodeActState, PlaybookCode, PlaybookPlan, PlaybookMeta
|
|
23
24
|
from universal_mcp.agents.codeact0.tools import (
|
|
24
25
|
create_meta_tools,
|
|
25
26
|
enter_playbook_mode,
|
|
@@ -28,6 +29,7 @@ from universal_mcp.agents.codeact0.tools import (
|
|
|
28
29
|
from universal_mcp.agents.codeact0.utils import add_tools
|
|
29
30
|
from universal_mcp.agents.llm import load_chat_model
|
|
30
31
|
from universal_mcp.agents.utils import convert_tool_ids_to_dict, filter_retry_on, get_message_text
|
|
32
|
+
from universal_mcp.agents.codeact0.utils import get_connected_apps_string
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
class CodeActPlaybookAgent(BaseAgent):
|
|
@@ -37,7 +39,6 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
37
39
|
instructions: str,
|
|
38
40
|
model: str,
|
|
39
41
|
memory: BaseCheckpointSaver | None = None,
|
|
40
|
-
tools: ToolConfig | None = None,
|
|
41
42
|
registry: ToolRegistry | None = None,
|
|
42
43
|
playbook_registry: object | None = None,
|
|
43
44
|
sandbox_timeout: int = 20,
|
|
@@ -52,18 +53,18 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
52
53
|
)
|
|
53
54
|
self.model_instance = load_chat_model(model)
|
|
54
55
|
self.playbook_model_instance = load_chat_model("azure/gpt-4.1")
|
|
55
|
-
self.tools_config = tools or {}
|
|
56
56
|
self.registry = registry
|
|
57
57
|
self.playbook_registry = playbook_registry
|
|
58
58
|
self.playbook = playbook_registry.get_agent() if playbook_registry else None
|
|
59
|
+
self.tools_config = self.playbook.tools if self.playbook else {}
|
|
59
60
|
self.eval_fn = eval_unsafe
|
|
60
61
|
self.sandbox_timeout = sandbox_timeout
|
|
61
62
|
self.default_tools = {
|
|
62
63
|
"llm": ["generate_text", "classify_data", "extract_data", "call_llm"],
|
|
63
|
-
"markitdown": ["convert_to_markdown"],
|
|
64
64
|
}
|
|
65
65
|
add_tools(self.tools_config, self.default_tools)
|
|
66
66
|
|
|
67
|
+
|
|
67
68
|
async def _build_graph(self):
|
|
68
69
|
meta_tools = create_meta_tools(self.registry)
|
|
69
70
|
additional_tools = [smart_print, meta_tools["web_search"]]
|
|
@@ -151,7 +152,7 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
151
152
|
else:
|
|
152
153
|
raise Exception(
|
|
153
154
|
f"Unexpected tool call: {tool_call['name']}. "
|
|
154
|
-
"tool calls must be one of 'enter_playbook_mode', 'execute_ipython_cell', 'load_functions', or 'search_functions'"
|
|
155
|
+
"tool calls must be one of 'enter_playbook_mode', 'execute_ipython_cell', 'load_functions', or 'search_functions'. For using functions, call them in code using 'execute_ipython_cell'."
|
|
155
156
|
)
|
|
156
157
|
except Exception as e:
|
|
157
158
|
tool_result = str(e)
|
|
@@ -167,7 +168,7 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
167
168
|
self.tools_config.extend(new_tool_ids)
|
|
168
169
|
self.exported_tools = await self.registry.export_tools(new_tool_ids, ToolFormat.LANGCHAIN)
|
|
169
170
|
self.final_instructions, self.tools_context = create_default_prompt(
|
|
170
|
-
self.exported_tools, self.additional_tools, self.instructions,
|
|
171
|
+
self.exported_tools, self.additional_tools, self.instructions, await get_connected_apps_string(self.registry), self.playbook
|
|
171
172
|
)
|
|
172
173
|
if ask_user:
|
|
173
174
|
tool_messages.append(AIMessage(content=ai_msg))
|
|
@@ -224,7 +225,47 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
224
225
|
|
|
225
226
|
t = user_text.lower()
|
|
226
227
|
if t == "yes, this is great":
|
|
227
|
-
|
|
228
|
+
self.meta_id = str(uuid.uuid4())
|
|
229
|
+
name, description = None, None
|
|
230
|
+
if self.playbook:
|
|
231
|
+
# Update flow: use existing name/description and do not re-generate
|
|
232
|
+
name = getattr(self.playbook, "name", None)
|
|
233
|
+
description = getattr(self.playbook, "description", None)
|
|
234
|
+
writer({
|
|
235
|
+
"type": "custom",
|
|
236
|
+
id: self.meta_id,
|
|
237
|
+
"name": "generating",
|
|
238
|
+
"data": {
|
|
239
|
+
"update": True,
|
|
240
|
+
"name": name,
|
|
241
|
+
"description": description,
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
else:
|
|
245
|
+
writer({
|
|
246
|
+
"type": "custom",
|
|
247
|
+
id: self.meta_id,
|
|
248
|
+
"name": "generating",
|
|
249
|
+
"data": {"update": False}
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
meta_instructions = self.instructions + PLAYBOOK_META_PROMPT
|
|
253
|
+
messages = [{"role": "system", "content": meta_instructions}] + state["messages"]
|
|
254
|
+
|
|
255
|
+
model_with_structured_output = self.playbook_model_instance.with_structured_output(PlaybookMeta)
|
|
256
|
+
meta_response = model_with_structured_output.invoke(messages)
|
|
257
|
+
meta = cast(PlaybookMeta, meta_response)
|
|
258
|
+
name, description = meta.name, meta.description
|
|
259
|
+
|
|
260
|
+
# Emit intermediary UI update with created name/description
|
|
261
|
+
writer({
|
|
262
|
+
"type": "custom",
|
|
263
|
+
id: self.meta_id,
|
|
264
|
+
"name": "generating",
|
|
265
|
+
"data": {"update": False, "name": name, "description": description}
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
return Command(goto="playbook", update={"playbook_mode": "generating", "playbook_name": name, "playbook_description": description})
|
|
228
269
|
if t == "i would like to modify the plan":
|
|
229
270
|
prompt_ai = AIMessage(content="What would you like to change about the plan? Let me know and I'll update the plan accordingly.", additional_kwargs={"stream": "true"})
|
|
230
271
|
return Command(update={"playbook_mode": "planning", "messages": [prompt_ai]})
|
|
@@ -235,13 +276,6 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
235
276
|
return Command(goto="call_model", update={"playbook_mode": "inactive"})
|
|
236
277
|
|
|
237
278
|
elif playbook_mode == "generating":
|
|
238
|
-
generate_id = str(uuid.uuid4())
|
|
239
|
-
writer({
|
|
240
|
-
"type": "custom",
|
|
241
|
-
id: generate_id,
|
|
242
|
-
"name": "generating",
|
|
243
|
-
"data": {"update": bool(self.playbook)}
|
|
244
|
-
})
|
|
245
279
|
generating_instructions = self.instructions + PLAYBOOK_GENERATING_PROMPT
|
|
246
280
|
messages = [{"role": "system", "content": generating_instructions}] + state["messages"]
|
|
247
281
|
|
|
@@ -256,6 +290,10 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
256
290
|
else:
|
|
257
291
|
function_name = "generated_playbook"
|
|
258
292
|
|
|
293
|
+
# Use generated metadata if available
|
|
294
|
+
final_name = state.get("playbook_name") or function_name
|
|
295
|
+
final_description = state.get("playbook_description") or f"Generated playbook: {function_name}"
|
|
296
|
+
|
|
259
297
|
# Save or update an Agent using the helper registry
|
|
260
298
|
try:
|
|
261
299
|
if not self.playbook_registry:
|
|
@@ -271,8 +309,8 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
271
309
|
tool_dict = convert_tool_ids_to_dict(state["selected_tool_ids"])
|
|
272
310
|
|
|
273
311
|
res = self.playbook_registry.upsert_agent(
|
|
274
|
-
name=
|
|
275
|
-
description=
|
|
312
|
+
name=final_name,
|
|
313
|
+
description=final_description,
|
|
276
314
|
instructions=instructions_payload,
|
|
277
315
|
tools=tool_dict,
|
|
278
316
|
visibility="private",
|
|
@@ -282,11 +320,25 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
282
320
|
|
|
283
321
|
writer({
|
|
284
322
|
"type": "custom",
|
|
285
|
-
id:
|
|
323
|
+
id: self.meta_id,
|
|
286
324
|
"name": "generating",
|
|
287
|
-
"data": {
|
|
325
|
+
"data": {
|
|
326
|
+
"id": str(res.id),
|
|
327
|
+
"update": bool(self.playbook),
|
|
328
|
+
"name": final_name,
|
|
329
|
+
"description": final_description,
|
|
330
|
+
}
|
|
288
331
|
})
|
|
289
|
-
mock_assistant_message = AIMessage(
|
|
332
|
+
mock_assistant_message = AIMessage(
|
|
333
|
+
content=json.dumps(response.dict()),
|
|
334
|
+
additional_kwargs={
|
|
335
|
+
"type": "generating",
|
|
336
|
+
"id": str(res.id),
|
|
337
|
+
"update": bool(self.playbook),
|
|
338
|
+
"name": final_name,
|
|
339
|
+
"description": final_description,
|
|
340
|
+
},
|
|
341
|
+
)
|
|
290
342
|
|
|
291
343
|
return Command(
|
|
292
344
|
update={"messages": [mock_assistant_message], "playbook_mode": "normal"}
|
|
@@ -298,7 +350,7 @@ class CodeActPlaybookAgent(BaseAgent):
|
|
|
298
350
|
self.tools_config.extend(state.get("selected_tool_ids", []))
|
|
299
351
|
self.exported_tools = await self.registry.export_tools(self.tools_config, ToolFormat.LANGCHAIN)
|
|
300
352
|
self.final_instructions, self.tools_context = create_default_prompt(
|
|
301
|
-
self.exported_tools, self.additional_tools, self.instructions,
|
|
353
|
+
self.exported_tools, self.additional_tools, self.instructions, await get_connected_apps_string(self.registry), self.playbook
|
|
302
354
|
)
|
|
303
355
|
if state.get("playbook_mode") in ["planning", "confirming", "generating"]:
|
|
304
356
|
return "playbook"
|
|
@@ -3,6 +3,7 @@ import re
|
|
|
3
3
|
from collections.abc import Sequence
|
|
4
4
|
|
|
5
5
|
from langchain_core.tools import StructuredTool
|
|
6
|
+
from universal_mcp.agents.codeact0.utils import schema_to_signature
|
|
6
7
|
|
|
7
8
|
uneditable_prompt = """
|
|
8
9
|
You are **Ruzo**, an AI Assistant created by AgentR — a creative, straight-forward, and direct principal software engineer with access to tools.
|
|
@@ -97,6 +98,26 @@ The parameters of the function should be the same as the final confirmed playboo
|
|
|
97
98
|
"""
|
|
98
99
|
|
|
99
100
|
|
|
101
|
+
PLAYBOOK_META_PROMPT = """
|
|
102
|
+
You are preparing metadata for a reusable playbook based on the confirmed step-by-step plan.
|
|
103
|
+
|
|
104
|
+
TASK: Create a concise, human-friendly name and a short description for the playbook.
|
|
105
|
+
|
|
106
|
+
INPUTS:
|
|
107
|
+
- Conversation context and plan steps will be provided in prior messages
|
|
108
|
+
|
|
109
|
+
REQUIREMENTS:
|
|
110
|
+
1. Name: 3-6 words, Title Case, no punctuation except hyphens if needed
|
|
111
|
+
2. Description: Single sentence, <= 140 characters, clearly states what the playbook does
|
|
112
|
+
|
|
113
|
+
OUTPUT: Return ONLY a JSON object with exactly these keys:
|
|
114
|
+
{
|
|
115
|
+
"name": "...",
|
|
116
|
+
"description": "..."
|
|
117
|
+
}
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
|
|
100
121
|
def make_safe_function_name(name: str) -> str:
|
|
101
122
|
"""Convert a tool name to a valid Python function name."""
|
|
102
123
|
# Replace non-alphanumeric characters with underscores
|
|
@@ -114,14 +135,31 @@ def create_default_prompt(
|
|
|
114
135
|
tools: Sequence[StructuredTool],
|
|
115
136
|
additional_tools: Sequence[StructuredTool],
|
|
116
137
|
base_prompt: str | None = None,
|
|
138
|
+
apps_string: str | None = None,
|
|
117
139
|
playbook: object | None = None,
|
|
118
140
|
):
|
|
119
|
-
system_prompt = uneditable_prompt.strip()
|
|
120
|
-
|
|
121
|
-
|
|
141
|
+
system_prompt = uneditable_prompt.strip()
|
|
142
|
+
if apps_string:
|
|
143
|
+
system_prompt += f"\n\n**Connected external applications (These apps have been logged into by the user):**\n{apps_string}\n\n Use `search_functions` to search for functions you can perform using the above. You can also discover more applications using the `search_functions` tool to find additional tools and integrations, if required.\n"
|
|
144
|
+
system_prompt += "\n\nIn addition to the Python Standard Library, you can use the following external functions:\n"
|
|
145
|
+
|
|
122
146
|
tools_context = {}
|
|
123
147
|
|
|
124
|
-
for tool in tools
|
|
148
|
+
for tool in tools:
|
|
149
|
+
if hasattr(tool, "func") and tool.func is not None:
|
|
150
|
+
tool_callable = tool.func
|
|
151
|
+
is_async = False
|
|
152
|
+
elif hasattr(tool, "coroutine") and tool.coroutine is not None:
|
|
153
|
+
tool_callable = tool.coroutine
|
|
154
|
+
is_async = True
|
|
155
|
+
system_prompt += f'''{"async " if is_async else ""}{schema_to_signature(tool.args, tool.name)}:
|
|
156
|
+
"""{tool.description}"""
|
|
157
|
+
...
|
|
158
|
+
'''
|
|
159
|
+
safe_name = make_safe_function_name(tool.name)
|
|
160
|
+
tools_context[safe_name] = tool_callable
|
|
161
|
+
|
|
162
|
+
for tool in additional_tools:
|
|
125
163
|
if hasattr(tool, "func") and tool.func is not None:
|
|
126
164
|
tool_callable = tool.func
|
|
127
165
|
is_async = False
|
|
@@ -131,7 +169,7 @@ def create_default_prompt(
|
|
|
131
169
|
system_prompt += f'''{"async " if is_async else ""}def {tool.name} {str(inspect.signature(tool_callable))}:
|
|
132
170
|
"""{tool.description}"""
|
|
133
171
|
...
|
|
134
|
-
|
|
172
|
+
'''
|
|
135
173
|
safe_name = make_safe_function_name(tool.name)
|
|
136
174
|
tools_context[safe_name] = tool_callable
|
|
137
175
|
|
|
@@ -12,6 +12,11 @@ class PlaybookCode(BaseModel):
|
|
|
12
12
|
code: str = Field(description="The Python code for the playbook.")
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
class PlaybookMeta(BaseModel):
|
|
16
|
+
name: str = Field(description="Concise, title-cased playbook name (3-6 words).")
|
|
17
|
+
description: str = Field(description="Short, one-sentence description (<= 140 chars).")
|
|
18
|
+
|
|
19
|
+
|
|
15
20
|
def _enqueue(left: list, right: list) -> list:
|
|
16
21
|
"""Treat left as a FIFO queue, append new items from right (preserve order),
|
|
17
22
|
keep items unique, and cap total size to 20 (drop oldest items)."""
|
|
@@ -47,3 +52,7 @@ class CodeActState(AgentState):
|
|
|
47
52
|
"""Queue for tools exported from registry"""
|
|
48
53
|
plan: list[str] | None
|
|
49
54
|
"""Plan for the playbook agent."""
|
|
55
|
+
playbook_name: str | None
|
|
56
|
+
"""Generated playbook name after confirmation."""
|
|
57
|
+
playbook_description: str | None
|
|
58
|
+
"""Generated short description after confirmation."""
|
|
@@ -14,12 +14,6 @@ def enter_playbook_mode():
|
|
|
14
14
|
"""Call this function to enter playbook mode. Playbook mode is when the user wants to store a repeated task as a script with some inputs for the future."""
|
|
15
15
|
return
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
def exit_playbook_mode():
|
|
19
|
-
"""Call this function to exit playbook mode. Playbook mode is when the user wants to store a repeated task as a script with some inputs for the future."""
|
|
20
|
-
return
|
|
21
|
-
|
|
22
|
-
|
|
23
17
|
def create_meta_tools(tool_registry: AgentrRegistry) -> dict[str, Any]:
|
|
24
18
|
"""Create the meta tools for searching and loading tools"""
|
|
25
19
|
|
|
@@ -70,9 +64,10 @@ def create_meta_tools(tool_registry: AgentrRegistry) -> dict[str, Any]:
|
|
|
70
64
|
|
|
71
65
|
canonical_app_id = None
|
|
72
66
|
found_tools_result = []
|
|
67
|
+
THRESHOLD = 0.8
|
|
73
68
|
|
|
74
69
|
if app_id:
|
|
75
|
-
relevant_apps = await registry.search_apps(query=app_id, distance_threshold=
|
|
70
|
+
relevant_apps = await registry.search_apps(query=app_id, distance_threshold=THRESHOLD)
|
|
76
71
|
if not relevant_apps:
|
|
77
72
|
return {
|
|
78
73
|
"found_tools": [],
|
|
@@ -104,11 +99,11 @@ def create_meta_tools(tool_registry: AgentrRegistry) -> dict[str, Any]:
|
|
|
104
99
|
prioritized_app_id_list = [canonical_app_id]
|
|
105
100
|
else:
|
|
106
101
|
# 1. Perform an initial broad search for tools.
|
|
107
|
-
initial_tool_search_tasks = [registry.search_tools(query=q, distance_threshold=
|
|
102
|
+
initial_tool_search_tasks = [registry.search_tools(query=q, distance_threshold=THRESHOLD) for q in queries]
|
|
108
103
|
initial_tool_results = await asyncio.gather(*initial_tool_search_tasks)
|
|
109
104
|
|
|
110
105
|
# 2. Search for relevant apps.
|
|
111
|
-
app_search_tasks = [registry.search_apps(query=q, distance_threshold=
|
|
106
|
+
app_search_tasks = [registry.search_apps(query=q, distance_threshold=THRESHOLD) for q in queries]
|
|
112
107
|
app_search_results = await asyncio.gather(*app_search_tasks)
|
|
113
108
|
|
|
114
109
|
# 3. Create a prioritized list of app IDs for the final search.
|
|
@@ -131,7 +126,7 @@ def create_meta_tools(tool_registry: AgentrRegistry) -> dict[str, Any]:
|
|
|
131
126
|
for app_id_to_search in prioritized_app_id_list:
|
|
132
127
|
for query in queries:
|
|
133
128
|
final_tool_search_tasks.append(
|
|
134
|
-
registry.search_tools(query=query, app_id=app_id_to_search, distance_threshold=
|
|
129
|
+
registry.search_tools(query=query, app_id=app_id_to_search, distance_threshold=THRESHOLD)
|
|
135
130
|
)
|
|
136
131
|
query_results = await asyncio.gather(*final_tool_search_tasks)
|
|
137
132
|
|
|
@@ -209,29 +204,27 @@ def create_meta_tools(tool_registry: AgentrRegistry) -> dict[str, Any]:
|
|
|
209
204
|
|
|
210
205
|
@tool
|
|
211
206
|
async def web_search(query: str) -> dict:
|
|
212
|
-
"""
|
|
213
|
-
Useful when you need information from a wide range of real-time sources on the web.
|
|
214
|
-
Do not use this when you need to access contents of a specific webpage.
|
|
207
|
+
"""
|
|
208
|
+
Get an LLM answer to a question informed by Exa search results. Useful when you need information from a wide range of real-time sources on the web. Do not use this when you need to access contents of a specific webpage.
|
|
215
209
|
|
|
216
|
-
This tool performs
|
|
217
|
-
1. Provides a **direct answer** for factual queries
|
|
210
|
+
This tool performs an Exa `/answer` request, which:
|
|
211
|
+
1. Provides a **direct answer** for factual queries (e.g., "What is the capital of France?" → "Paris")
|
|
218
212
|
2. Generates a **summary with citations** for open-ended questions
|
|
213
|
+
(e.g., "What is the state of AI in healthcare?" → A detailed summary with source links)
|
|
219
214
|
|
|
220
215
|
Args:
|
|
221
216
|
query (str): The question or topic to answer.
|
|
222
|
-
|
|
223
217
|
Returns:
|
|
224
|
-
dict: A structured response containing:
|
|
225
|
-
- answer (str): Generated answer
|
|
226
|
-
- citations (list[
|
|
218
|
+
dict: A structured response containing only:
|
|
219
|
+
- answer (str): Generated answer
|
|
220
|
+
- citations (list[dict]): List of cited sources
|
|
227
221
|
"""
|
|
228
222
|
await tool_registry.export_tools(["exa__answer"], ToolFormat.LANGCHAIN)
|
|
229
223
|
response = await tool_registry.call_tool("exa__answer", {"query": query, "text": True})
|
|
230
|
-
logger.info(f"Web search response: {response}")
|
|
231
224
|
|
|
232
225
|
# Extract only desired fields
|
|
233
226
|
return {
|
|
234
|
-
"answer": response.get("
|
|
227
|
+
"answer": response.get("answer"),
|
|
235
228
|
"citations": response.get("citations", []),
|
|
236
229
|
}
|
|
237
230
|
|
|
@@ -279,7 +272,7 @@ async def get_valid_tools(tool_ids: list[str], registry: AgentrRegistry) -> tupl
|
|
|
279
272
|
continue
|
|
280
273
|
if app not in connected_apps and app not in unconnected:
|
|
281
274
|
unconnected.add(app)
|
|
282
|
-
text = registry.authorise_app(app_id=app)
|
|
275
|
+
text = await registry.authorise_app(app_id=app)
|
|
283
276
|
start = text.find(":") + 1
|
|
284
277
|
end = text.find(". R", start)
|
|
285
278
|
url = text[start:end].strip()
|
|
@@ -333,35 +333,48 @@ def inject_context(
|
|
|
333
333
|
return namespace
|
|
334
334
|
|
|
335
335
|
|
|
336
|
-
def schema_to_signature(schema: dict, func_name="my_function") -> str:
|
|
336
|
+
def schema_to_signature(schema: dict, func_name: str = "my_function") -> str:
|
|
337
|
+
"""
|
|
338
|
+
Convert a JSON schema into a Python-style function signature string.
|
|
339
|
+
Handles fields with `type`, `anyOf`, defaults, and missing metadata safely.
|
|
340
|
+
"""
|
|
337
341
|
type_map = {
|
|
338
342
|
"integer": "int",
|
|
339
343
|
"string": "str",
|
|
340
344
|
"boolean": "bool",
|
|
341
345
|
"null": "None",
|
|
346
|
+
"number": "float",
|
|
347
|
+
"array": "list",
|
|
348
|
+
"object": "dict",
|
|
342
349
|
}
|
|
343
350
|
|
|
344
351
|
params = []
|
|
345
352
|
for name, meta in schema.items():
|
|
346
|
-
|
|
347
|
-
|
|
353
|
+
if not isinstance(meta, dict):
|
|
354
|
+
typ = "Any"
|
|
355
|
+
elif "type" in meta:
|
|
348
356
|
typ = type_map.get(meta["type"], "Any")
|
|
349
357
|
elif "anyOf" in meta:
|
|
350
|
-
types = [
|
|
351
|
-
|
|
358
|
+
types = []
|
|
359
|
+
for t in meta["anyOf"]:
|
|
360
|
+
if not isinstance(t, dict):
|
|
361
|
+
continue
|
|
362
|
+
t_type = t.get("type")
|
|
363
|
+
types.append(type_map.get(t_type, "Any") if t_type else "Any")
|
|
364
|
+
typ = " | ".join(sorted(set(types))) if types else "Any"
|
|
352
365
|
else:
|
|
353
366
|
typ = "Any"
|
|
354
367
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
368
|
+
# Handle defaults gracefully
|
|
369
|
+
default = meta.get("default")
|
|
370
|
+
if default is None:
|
|
371
|
+
params.append(f"{name}: {typ}")
|
|
372
|
+
else:
|
|
373
|
+
params.append(f"{name}: {typ} = {repr(default)}")
|
|
359
374
|
|
|
360
|
-
# join into signature
|
|
361
375
|
param_str = ",\n ".join(params)
|
|
362
376
|
return f"def {func_name}(\n {param_str},\n):"
|
|
363
377
|
|
|
364
|
-
|
|
365
378
|
def smart_truncate(
|
|
366
379
|
output: str, max_chars_full: int = 2000, max_lines_headtail: int = 20, summary_threshold: int = 10000
|
|
367
380
|
) -> str:
|
|
@@ -394,3 +407,27 @@ def smart_truncate(
|
|
|
394
407
|
truncated = truncated[:summary_threshold] + "\n... [output truncated to fit context] ..."
|
|
395
408
|
|
|
396
409
|
return truncated
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
async def get_connected_apps_string(registry) -> str:
|
|
413
|
+
"""Get a formatted string of connected applications from the registry."""
|
|
414
|
+
if not registry:
|
|
415
|
+
return ""
|
|
416
|
+
|
|
417
|
+
try:
|
|
418
|
+
# Get connected apps from registry
|
|
419
|
+
connections = await registry.list_connected_apps()
|
|
420
|
+
if not connections:
|
|
421
|
+
return "No applications are currently connected."
|
|
422
|
+
|
|
423
|
+
# Extract app names from connections
|
|
424
|
+
connected_app_ids = {connection["app_id"] for connection in connections}
|
|
425
|
+
|
|
426
|
+
# Format the apps list
|
|
427
|
+
apps_list = []
|
|
428
|
+
for app_id in connected_app_ids:
|
|
429
|
+
apps_list.append(f"- {app_id}")
|
|
430
|
+
|
|
431
|
+
return "\n".join(apps_list)
|
|
432
|
+
except Exception:
|
|
433
|
+
return "Unable to retrieve connected applications."
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: universal-mcp-agents
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.23rc1
|
|
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
|
|
@@ -22,15 +22,15 @@ universal_mcp/agents/builder/prompts.py,sha256=8Xs6uzTUHguDRngVMLak3lkXFkk2VV_uQ
|
|
|
22
22
|
universal_mcp/agents/builder/state.py,sha256=7DeWllxfN-yD6cd9wJ3KIgjO8TctkJvVjAbZT8W_zqk,922
|
|
23
23
|
universal_mcp/agents/codeact0/__init__.py,sha256=8-fvUo1Sm6dURGI-lW-X3Kd78LqySYbb5NMkNJ4NDwg,76
|
|
24
24
|
universal_mcp/agents/codeact0/__main__.py,sha256=_7qSz97YnRgYJTESkALS5_eBIGHiMjA5rhr3IAeBvVo,896
|
|
25
|
-
universal_mcp/agents/codeact0/agent.py,sha256
|
|
25
|
+
universal_mcp/agents/codeact0/agent.py,sha256=--cAjQ05VntXga2ofpnL9oFXAk6ANZ2SftwtLkQgy00,17844
|
|
26
26
|
universal_mcp/agents/codeact0/config.py,sha256=H-1woj_nhSDwf15F63WYn723y4qlRefXzGxuH81uYF0,2215
|
|
27
27
|
universal_mcp/agents/codeact0/langgraph_agent.py,sha256=8nz2wq-LexImx-l1y9_f81fK72IQetnCeljwgnduNGY,420
|
|
28
28
|
universal_mcp/agents/codeact0/llm_tool.py,sha256=-pAz04OrbZ_dJ2ueysT1qZd02DrbLY4EbU0tiuF_UNU,798
|
|
29
|
-
universal_mcp/agents/codeact0/prompts.py,sha256=
|
|
29
|
+
universal_mcp/agents/codeact0/prompts.py,sha256=Gk6WTx2X7IOlbC3wYtGwKWDoeUtgpkMeiyfhj6LEq2I,11221
|
|
30
30
|
universal_mcp/agents/codeact0/sandbox.py,sha256=Xw4tbUV_6haYIZZvteJi6lIYsW6ni_3DCRCOkslTKgM,4459
|
|
31
|
-
universal_mcp/agents/codeact0/state.py,sha256=
|
|
32
|
-
universal_mcp/agents/codeact0/tools.py,sha256=
|
|
33
|
-
universal_mcp/agents/codeact0/utils.py,sha256=
|
|
31
|
+
universal_mcp/agents/codeact0/state.py,sha256=co3BZBuMIt1FP2qzgsbsLkyKbddCG1ieKyAw9TAskSU,1944
|
|
32
|
+
universal_mcp/agents/codeact0/tools.py,sha256=1EBStJQQQCuNeWOqt0VP-XFiT0fE4oCR9nvUiwzRhe4,13164
|
|
33
|
+
universal_mcp/agents/codeact0/utils.py,sha256=PgisAxmqYIzimc4lFA1nV-R7gxkICIrtO1q0FQZ-UoY,17580
|
|
34
34
|
universal_mcp/agents/shared/__main__.py,sha256=XxH5qGDpgFWfq7fwQfgKULXGiUgeTp_YKfcxftuVZq8,1452
|
|
35
35
|
universal_mcp/agents/shared/prompts.py,sha256=yjP3zbbuKi87qCj21qwTTicz8TqtkKgnyGSeEjMu3ho,3761
|
|
36
36
|
universal_mcp/agents/shared/tool_node.py,sha256=DC9F-Ri28Pam0u3sXWNODVgmj9PtAEUb5qP1qOoGgfs,9169
|
|
@@ -39,6 +39,6 @@ universal_mcp/applications/filesystem/app.py,sha256=0TRjjm8YnslVRSmfkXI7qQOAlqWl
|
|
|
39
39
|
universal_mcp/applications/llm/__init__.py,sha256=_XGRxN3O1--ZS5joAsPf8IlI9Qa6negsJrwJ5VJXno0,46
|
|
40
40
|
universal_mcp/applications/llm/app.py,sha256=g9mK-luOLUshZzBGyQZMOHBeCSXmh2kCKir40YnsGUo,12727
|
|
41
41
|
universal_mcp/applications/ui/app.py,sha256=c7OkZsO2fRtndgAzAQbKu-1xXRuRp9Kjgml57YD2NR4,9459
|
|
42
|
-
universal_mcp_agents-0.1.
|
|
43
|
-
universal_mcp_agents-0.1.
|
|
44
|
-
universal_mcp_agents-0.1.
|
|
42
|
+
universal_mcp_agents-0.1.23rc1.dist-info/METADATA,sha256=Fa-ACWH1oawoVDBAbp2iSSDGSCRovD2rWP9e9lUhcYE,881
|
|
43
|
+
universal_mcp_agents-0.1.23rc1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
44
|
+
universal_mcp_agents-0.1.23rc1.dist-info/RECORD,,
|
|
File without changes
|