Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions ex_app/lib/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ def export_conversation(checkpointer):
return add_signature(checkpointer.serde.dumps(checkpointer.storage).decode('utf-8'), key)


def react(task, nc: Nextcloud):
safe_tools, dangerous_tools = get_tools(nc)
async def react(task, nc: Nextcloud):
safe_tools, dangerous_tools = await get_tools(nc)

model.bind_nextcloud(nc)

Expand All @@ -54,7 +54,7 @@ def react(task, nc: Nextcloud):
+ safe_tools
)

def call_model(
async def call_model(
state: AgentState,
config: RunnableConfig,
):
Expand All @@ -77,12 +77,12 @@ def call_model(
""".replace("{CURRENT_DATE}", current_date)
)

response = bound_model.invoke([system_prompt] + state["messages"], config)
response = await bound_model.ainvoke([system_prompt] + state["messages"], config)
# We return a list, because this will get added to the existing list
return {"messages": [response]}

checkpointer = MemorySaver()
graph = get_graph(call_model, safe_tools, dangerous_tools, checkpointer)
graph = await get_graph(call_model, safe_tools, dangerous_tools, checkpointer)

load_conversation(checkpointer, task['input']['conversation_token'])

Expand All @@ -105,7 +105,7 @@ def call_model(
else:
new_input = {"messages": [("user", task['input']['input'])]}

for event in graph.stream(new_input, thread, stream_mode="values"):
async for event in graph.astream(new_input, thread, stream_mode="values"):
last_message = event['messages'][-1]
for message in event['messages']:
if isinstance(message, HumanMessage):
Expand Down
2 changes: 1 addition & 1 deletion ex_app/lib/all_tools/audio2text.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from ex_app.lib.all_tools.lib.decorator import safe_tool


def get_tools(nc: Nextcloud):
async def get_tools(nc: Nextcloud):

@tool
@safe_tool
Expand Down
2 changes: 1 addition & 1 deletion ex_app/lib/all_tools/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from ex_app.lib.logger import log


def get_tools(nc: Nextcloud):
async def get_tools(nc: Nextcloud):

@tool
@safe_tool
Expand Down
2 changes: 1 addition & 1 deletion ex_app/lib/all_tools/contacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from ex_app.lib.all_tools.lib.decorator import safe_tool


def get_tools(nc: Nextcloud):
async def get_tools(nc: Nextcloud):
@tool
@safe_tool
def find_person_in_contacts(name: str) -> list[dict[str, typing.Any]]:
Expand Down
2 changes: 1 addition & 1 deletion ex_app/lib/all_tools/context_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from ex_app.lib.all_tools.lib.decorator import safe_tool


def get_tools(nc: Nextcloud):
async def get_tools(nc: Nextcloud):

@tool
@safe_tool
Expand Down
2 changes: 1 addition & 1 deletion ex_app/lib/all_tools/deck.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from ex_app.lib.logger import log


def get_tools(nc: Nextcloud):
async def get_tools(nc: Nextcloud):

@tool
@safe_tool
Expand Down
2 changes: 1 addition & 1 deletion ex_app/lib/all_tools/doc-gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from ex_app.lib.all_tools.lib.decorator import safe_tool


def get_tools(nc: Nextcloud):
async def get_tools(nc: Nextcloud):

@tool
@safe_tool
Expand Down
2 changes: 1 addition & 1 deletion ex_app/lib/all_tools/duckduckgo.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from ex_app.lib.all_tools.lib.decorator import safe_tool


def get_tools(nc: Nextcloud):
async def get_tools(nc: Nextcloud):

web_search = DuckDuckGoSearchResults(output_format="list")
return [
Expand Down
2 changes: 1 addition & 1 deletion ex_app/lib/all_tools/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from ex_app.lib.all_tools.lib.decorator import safe_tool, dangerous_tool


def get_tools(nc: Nextcloud):
async def get_tools(nc: Nextcloud):

@tool
@safe_tool
Expand Down
2 changes: 1 addition & 1 deletion ex_app/lib/all_tools/here.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from ex_app.lib.all_tools.lib.decorator import safe_tool


def get_tools(nc: Nextcloud):
async def get_tools(nc: Nextcloud):

@tool
@safe_tool
Expand Down
2 changes: 1 addition & 1 deletion ex_app/lib/all_tools/image_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from ex_app.lib.all_tools.lib.decorator import safe_tool


def get_tools(nc: Nextcloud):
async def get_tools(nc: Nextcloud):

@tool
@safe_tool
Expand Down
4 changes: 2 additions & 2 deletions ex_app/lib/all_tools/lib/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def decorator(func):
timestamp = {}

@wraps(func)
def wrapper(*args): # needs NextcloudApp as first arg
async def wrapper(*args): # needs NextcloudApp as first arg
nonlocal cached_result
nonlocal timestamp
user_id = args[0].user # cache result saved per user
Expand All @@ -33,7 +33,7 @@ def wrapper(*args): # needs NextcloudApp as first arg
del cached_result[user_id]
timestamp[user_id] = 0
# Call the function and cache the result
result = func(*args)
result = await func(*args)
cached_result[user_id] = result
timestamp[user_id] = current_time
return result
Expand Down
2 changes: 1 addition & 1 deletion ex_app/lib/all_tools/mail.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from ex_app.lib.logger import log


def get_tools(nc: Nextcloud):
async def get_tools(nc: Nextcloud):
@tool
@dangerous_tool
def send_email(subject: str, body: str, account_id: int, from_email: str, to_emails: list[str]):
Expand Down
38 changes: 38 additions & 0 deletions ex_app/lib/all_tools/mcp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: AGPL-3.0-or-later
from json import JSONDecodeError

from langchain_mcp_adapters.client import MultiServerMCPClient
from nc_py_api import Nextcloud
import json
from ex_app.lib.logger import log
from nc_py_api.ex_app import LogLvl
import asyncio
import traceback


async def get_tools(nc: Nextcloud):
mcp_json = nc.appconfig_ex.get_value("mcp_config", "{}")
try:
mcp_config = json.loads(mcp_json)
except JSONDecodeError:
log(nc, LogLvl.ERROR, "Invalid MCP json config: " + mcp_json)
mcp_config = {}
try:
server = MultiServerMCPClient(mcp_config)
tools = await asyncio.wait_for(server.get_tools(), timeout=120)
for tool in tools:
tool.name = "mcp_" + tool.name
return tools
except Exception as e:
tb_str = "".join(traceback.format_exception(e))
log(nc, LogLvl.ERROR, "Failed to load MCP servers: " + tb_str)
return []


def get_category_name():
return "MCP Server"


def is_available(nc: Nextcloud):
return True
2 changes: 1 addition & 1 deletion ex_app/lib/all_tools/openproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from ex_app.lib.all_tools.lib.decorator import safe_tool, dangerous_tool


def get_tools(nc: Nextcloud):
async def get_tools(nc: Nextcloud):
@tool
@safe_tool
def list_projects():
Expand Down
2 changes: 1 addition & 1 deletion ex_app/lib/all_tools/openstreetmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from ex_app.lib.all_tools.lib.decorator import safe_tool


def get_tools(nc: Nextcloud):
async def get_tools(nc: Nextcloud):
@tool
@safe_tool
def get_coordinates_for_address(address: str) -> (str, str):
Expand Down
2 changes: 1 addition & 1 deletion ex_app/lib/all_tools/talk.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from ex_app.lib.all_tools.lib.decorator import safe_tool, dangerous_tool


def get_tools(nc: Nextcloud):
async def get_tools(nc: Nextcloud):
@tool
@safe_tool
def list_talk_conversations():
Expand Down
2 changes: 1 addition & 1 deletion ex_app/lib/all_tools/weather.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from ex_app.lib.all_tools.lib.decorator import safe_tool


def get_tools(nc: Nextcloud):
async def get_tools(nc: Nextcloud):
@tool
@safe_tool
def get_current_weather_for_coordinates(lat: str, lon: str) -> dict[str, typing.Any]:
Expand Down
2 changes: 1 addition & 1 deletion ex_app/lib/all_tools/youtube.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from ex_app.lib.all_tools.lib.decorator import safe_tool


def get_tools(nc: Nextcloud):
async def get_tools(nc: Nextcloud):

yt_search = YouTubeSearchTool()
return [
Expand Down
2 changes: 1 addition & 1 deletion ex_app/lib/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def create_tool_node_with_fallback(tools: list) -> dict:
[RunnableLambda(handle_tool_error)], exception_key="error"
)

def get_graph(call_model, safe_tools, dangerous_tools, checkpointer):
async def get_graph(call_model, safe_tools, dangerous_tools, checkpointer):
dangerous_tool_names = {tool.name: tool for tool in dangerous_tools}
safe_tool_names = {tool.name: tool for tool in safe_tools}

Expand Down
Loading
Loading