Skip to content

Commit e2e7561

Browse files
author
Davidson Gomes
committed
Merge branch 'develop' of github.com:EvolutionAPI/evo-ai into develop
2 parents cf24a7c + 3e8c322 commit e2e7561

File tree

7 files changed

+136
-14
lines changed

7 files changed

+136
-14
lines changed

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ version: "3.8"
22

33
services:
44
api:
5-
image: evo-ai-api:latest
5+
image: evoapicloud/evo-ai:latest
66
depends_on:
77
- postgres
88
- redis

src/api/mcp_server_routes.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"""
2929

3030
from fastapi import APIRouter, Depends, HTTPException, status
31+
from starlette.concurrency import run_in_threadpool
3132
from sqlalchemy.orm import Session
3233
from src.config.database import get_db
3334
from typing import List
@@ -54,7 +55,7 @@
5455
responses={404: {"description": "Not found"}},
5556
)
5657

57-
58+
# Last edited by Arley Peter on 2025-05-17
5859
@router.post("/", response_model=MCPServer, status_code=status.HTTP_201_CREATED)
5960
async def create_mcp_server(
6061
server: MCPServerCreate,
@@ -64,7 +65,7 @@ async def create_mcp_server(
6465
# Only administrators can create MCP servers
6566
await verify_admin(payload)
6667

67-
return mcp_server_service.create_mcp_server(db, server)
68+
return await run_in_threadpool(mcp_server_service.create_mcp_server, db, server)
6869

6970

7071
@router.get("/", response_model=List[MCPServer])

src/schemas/schemas.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,14 +262,14 @@ class ToolConfig(BaseModel):
262262
inputModes: List[str] = Field(default_factory=list)
263263
outputModes: List[str] = Field(default_factory=list)
264264

265-
265+
# Last edited by Arley Peter on 2025-05-17
266266
class MCPServerBase(BaseModel):
267267
name: str
268268
description: Optional[str] = None
269269
config_type: str = Field(default="studio")
270270
config_json: Dict[str, Any] = Field(default_factory=dict)
271271
environments: Dict[str, Any] = Field(default_factory=dict)
272-
tools: List[ToolConfig] = Field(default_factory=list)
272+
tools: Optional[List[ToolConfig]] = Field(default_factory=list)
273273
type: str = Field(default="official")
274274

275275

src/services/adk/custom_agents/workflow_agent.py

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
│ Creation date: May 13, 2025 │
77
│ Contact: [email protected]
88
├──────────────────────────────────────────────────────────────────────────────┤
9+
│ @contributors: │
10+
│ Victor Calazans - delay node implementation (May 17, 2025) │
11+
├──────────────────────────────────────────────────────────────────────────────┤
912
│ @copyright © Evolution API 2025. All rights reserved. │
1013
│ Licensed under the Apache License, Version 2.0 │
1114
│ │
@@ -320,10 +323,9 @@ async def condition_node_function(
320323
)
321324
]
322325
),
323-
)
324-
]
326+
) ]
325327
content = content + condition_content
326-
328+
327329
yield {
328330
"content": content,
329331
"status": "condition_evaluated",
@@ -332,7 +334,7 @@ async def condition_node_function(
332334
"conversation_history": conversation_history,
333335
"session_id": session_id,
334336
}
335-
337+
336338
async def message_node_function(
337339
state: State, node_id: str, node_data: Dict[str, Any]
338340
) -> AsyncGenerator[State, None]:
@@ -365,15 +367,63 @@ async def message_node_function(
365367
"status": "message_added",
366368
"node_outputs": node_outputs,
367369
"cycle_count": state.get("cycle_count", 0),
370+
"conversation_history": conversation_history, "session_id": session_id,
371+
}
372+
373+
async def delay_node_function(
374+
state: State, node_id: str, node_data: Dict[str, Any]
375+
) -> AsyncGenerator[State, None]:
376+
delay_data = node_data.get("delay", {})
377+
delay_value = delay_data.get("value", 0)
378+
delay_unit = delay_data.get("unit", "seconds")
379+
delay_description = delay_data.get("description", "")
380+
381+
# Convert to seconds based on unit
382+
delay_seconds = delay_value
383+
if delay_unit == "minutes":
384+
delay_seconds = delay_value * 60
385+
elif delay_unit == "hours":
386+
delay_seconds = delay_value * 3600
387+
388+
label = node_data.get("label", "delay_node")
389+
print(f"\n⏱️ DELAY-NODE: {delay_value} {delay_unit} - {delay_description}")
390+
391+
content = state.get("content", [])
392+
session_id = state.get("session_id", "")
393+
conversation_history = state.get("conversation_history", [])
394+
395+
# Store node output information
396+
node_outputs = state.get("node_outputs", {})
397+
node_outputs[node_id] = {
398+
"delay_value": delay_value,
399+
"delay_unit": delay_unit,
400+
"delay_seconds": delay_seconds,
401+
"delay_start_time": datetime.now().isoformat(),
402+
}
403+
404+
# Actually perform the delay
405+
import asyncio
406+
await asyncio.sleep(delay_seconds)
407+
408+
409+
# Update node outputs with completion information
410+
node_outputs[node_id]["delay_end_time"] = datetime.now().isoformat()
411+
node_outputs[node_id]["delay_completed"] = True
412+
413+
yield {
414+
"content": content,
415+
"status": "delay_completed",
416+
"node_outputs": node_outputs, "cycle_count": state.get("cycle_count", 0),
368417
"conversation_history": conversation_history,
369418
"session_id": session_id,
370419
}
371-
420+
372421
return {
373422
"start-node": start_node_function,
374423
"agent-node": agent_node_function,
375424
"condition-node": condition_node_function,
376425
"message-node": message_node_function,
426+
"delay-node": delay_node_function,
377427
}
378428

379429
def _evaluate_condition(self, condition: Dict[str, Any], state: State) -> bool:

src/services/adk/custom_tools.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from google.adk.tools import FunctionTool
3232
import requests
3333
import json
34+
import urllib.parse
3435
from src.utils.logger import setup_logger
3536

3637
logger = setup_logger(__name__)
@@ -70,7 +71,9 @@ def http_tool(**kwargs):
7071
url = endpoint
7172
for param, value in path_params.items():
7273
if param in all_values:
73-
url = url.replace(f"{{{param}}}", str(all_values[param]))
74+
# URL encode the value for URL safe characters
75+
replacement_value = urllib.parse.quote(str(all_values[param]), safe='')
76+
url = url.replace(f"{{{param}}}", replacement_value)
7477

7578
# Process query parameters
7679
query_params_dict = {}
@@ -119,8 +122,12 @@ def http_tool(**kwargs):
119122
f"Error in the request: {response.status_code} - {response.text}"
120123
)
121124

122-
# Always returns the response as a string
123-
return json.dumps(response.json())
125+
# Try to parse the response as JSON, if it fails, return the text content
126+
try:
127+
return json.dumps(response.json())
128+
except ValueError:
129+
# Response is not JSON, return the text content
130+
return json.dumps({"content": response.text})
124131

125132
except Exception as e:
126133
logger.error(f"Error executing tool {name}: {str(e)}")

src/services/mcp_server_service.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from fastapi import HTTPException, status
3333
from src.models.models import MCPServer
3434
from src.schemas.schemas import MCPServerCreate
35+
from src.utils.mcp_discovery import discover_mcp_tools
3536
from typing import List, Optional
3637
import uuid
3738
import logging
@@ -72,8 +73,16 @@ def create_mcp_server(db: Session, server: MCPServerCreate) -> MCPServer:
7273
try:
7374
# Convert tools to JSON serializable format
7475
server_data = server.model_dump()
75-
server_data["tools"] = [tool.model_dump() for tool in server.tools]
7676

77+
# Last edited by Arley Peter on 2025-05-17
78+
supplied_tools = server_data.pop("tools", [])
79+
if not supplied_tools:
80+
discovered = discover_mcp_tools(server_data["config_json"])
81+
print(f"🔍 Found {len(discovered)} tools.")
82+
server_data["tools"] = discovered
83+
84+
else:
85+
server_data["tools"] = [tool.model_dump() for tool in supplied_tools]
7786
db_server = MCPServer(**server_data)
7887
db.add(db_server)
7988
db.commit()

src/utils/mcp_discovery.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
┌──────────────────────────────────────────────────────────────────────────────┐
3+
│ @author: Arley Peter │
4+
│ @file: mcp_discovery.py │
5+
│ Developed by: Arley Peter │
6+
│ Creation date: May 05, 2025 │
7+
├──────────────────────────────────────────────────────────────────────────────┤
8+
│ @copyright © Evolution API 2025. All rights reserved. │
9+
│ Licensed under the Apache License, Version 2.0 │
10+
│ │
11+
│ You may not use this file except in compliance with the License. │
12+
│ You may obtain a copy of the License at │
13+
│ │
14+
│ http://www.apache.org/licenses/LICENSE-2.0 │
15+
│ │
16+
│ Unless required by applicable law or agreed to in writing, software │
17+
│ distributed under the License is distributed on an "AS IS" BASIS, │
18+
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
19+
│ See the License for the specific language governing permissions and │
20+
│ limitations under the License. │
21+
├──────────────────────────────────────────────────────────────────────────────┤
22+
│ @important │
23+
│ For any future changes to the code in this file, it is recommended to │
24+
│ include, together with the modification, the information of the developer │
25+
│ who changed it and the date of modification. │
26+
└──────────────────────────────────────────────────────────────────────────────┘
27+
"""
28+
29+
from typing import List, Dict, Any
30+
import asyncio
31+
32+
async def _discover_async(config_json: Dict[str, Any]) -> List[Dict[str, Any]]:
33+
"""Return a list[dict] with the tool metadata advertised by the MCP server."""
34+
35+
from src.services.mcp_service import MCPService
36+
37+
service = MCPService()
38+
tools, exit_stack = await service._connect_to_mcp_server(config_json)
39+
serialised = [t.to_dict() if hasattr(t, "to_dict") else {
40+
"id": t.name,
41+
"name": t.name,
42+
"description": getattr(t, "description", t.name),
43+
"tags": getattr(t, "tags", []),
44+
"examples": getattr(t, "examples", []),
45+
"inputModes": getattr(t, "input_modes", ["text"]),
46+
"outputModes": getattr(t, "output_modes", ["text"]),
47+
} for t in tools]
48+
if exit_stack:
49+
await exit_stack.aclose()
50+
return serialised
51+
52+
53+
def discover_mcp_tools(config_json: Dict[str, Any]) -> List[Dict[str, Any]]:
54+
"""Sync wrapper so we can call it from a sync service function."""
55+
return asyncio.run(_discover_async(config_json))

0 commit comments

Comments
 (0)