From 8ed6becb66f93ec4dd892c21320691f488bd0f2c Mon Sep 17 00:00:00 2001 From: Lukas Schaefer Date: Mon, 28 Jul 2025 14:37:52 -0400 Subject: [PATCH 1/4] feat: allow for specifying mcp clients and convert functions to async Signed-off-by: Lukas Schaefer --- ex_app/lib/agent.py | 12 +- ex_app/lib/all_tools/audio2text.py | 2 +- ex_app/lib/all_tools/calendar.py | 2 +- ex_app/lib/all_tools/contacts.py | 2 +- ex_app/lib/all_tools/context_chat.py | 2 +- ex_app/lib/all_tools/deck.py | 2 +- ex_app/lib/all_tools/doc-gen.py | 2 +- ex_app/lib/all_tools/duckduckgo.py | 2 +- ex_app/lib/all_tools/files.py | 2 +- ex_app/lib/all_tools/here.py | 2 +- ex_app/lib/all_tools/image_gen.py | 2 +- ex_app/lib/all_tools/lib/decorator.py | 4 +- ex_app/lib/all_tools/mail.py | 2 +- ex_app/lib/all_tools/mcp.py | 35 +++ ex_app/lib/all_tools/openproject.py | 2 +- ex_app/lib/all_tools/openstreetmap.py | 2 +- ex_app/lib/all_tools/talk.py | 2 +- ex_app/lib/all_tools/weather.py | 2 +- ex_app/lib/all_tools/youtube.py | 2 +- ex_app/lib/graph.py | 2 +- ex_app/lib/main.py | 86 ++++--- ex_app/lib/tools.py | 7 +- poetry.lock | 338 +++++++++++++++++++++++++- pyproject.toml | 1 + 24 files changed, 441 insertions(+), 76 deletions(-) create mode 100644 ex_app/lib/all_tools/mcp.py diff --git a/ex_app/lib/agent.py b/ex_app/lib/agent.py index 5da413e..c6795ef 100644 --- a/ex_app/lib/agent.py +++ b/ex_app/lib/agent.py @@ -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) @@ -54,7 +54,7 @@ def react(task, nc: Nextcloud): + safe_tools ) - def call_model( + async def call_model( state: AgentState, config: RunnableConfig, ): @@ -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']) @@ -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): diff --git a/ex_app/lib/all_tools/audio2text.py b/ex_app/lib/all_tools/audio2text.py index 86d614a..1c0d4e6 100644 --- a/ex_app/lib/all_tools/audio2text.py +++ b/ex_app/lib/all_tools/audio2text.py @@ -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 diff --git a/ex_app/lib/all_tools/calendar.py b/ex_app/lib/all_tools/calendar.py index 6553c28..2ae8e87 100644 --- a/ex_app/lib/all_tools/calendar.py +++ b/ex_app/lib/all_tools/calendar.py @@ -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 diff --git a/ex_app/lib/all_tools/contacts.py b/ex_app/lib/all_tools/contacts.py index c1b0a49..f7c08ff 100644 --- a/ex_app/lib/all_tools/contacts.py +++ b/ex_app/lib/all_tools/contacts.py @@ -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]]: diff --git a/ex_app/lib/all_tools/context_chat.py b/ex_app/lib/all_tools/context_chat.py index e1cc304..837eccf 100644 --- a/ex_app/lib/all_tools/context_chat.py +++ b/ex_app/lib/all_tools/context_chat.py @@ -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 diff --git a/ex_app/lib/all_tools/deck.py b/ex_app/lib/all_tools/deck.py index cffff88..7a6e735 100644 --- a/ex_app/lib/all_tools/deck.py +++ b/ex_app/lib/all_tools/deck.py @@ -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 diff --git a/ex_app/lib/all_tools/doc-gen.py b/ex_app/lib/all_tools/doc-gen.py index b4eac17..c9d41e7 100644 --- a/ex_app/lib/all_tools/doc-gen.py +++ b/ex_app/lib/all_tools/doc-gen.py @@ -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 diff --git a/ex_app/lib/all_tools/duckduckgo.py b/ex_app/lib/all_tools/duckduckgo.py index cb78313..a46f587 100644 --- a/ex_app/lib/all_tools/duckduckgo.py +++ b/ex_app/lib/all_tools/duckduckgo.py @@ -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 [ diff --git a/ex_app/lib/all_tools/files.py b/ex_app/lib/all_tools/files.py index 75da7b8..4163283 100644 --- a/ex_app/lib/all_tools/files.py +++ b/ex_app/lib/all_tools/files.py @@ -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 diff --git a/ex_app/lib/all_tools/here.py b/ex_app/lib/all_tools/here.py index 4f85ea0..887ff1a 100644 --- a/ex_app/lib/all_tools/here.py +++ b/ex_app/lib/all_tools/here.py @@ -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 diff --git a/ex_app/lib/all_tools/image_gen.py b/ex_app/lib/all_tools/image_gen.py index 595eec8..979ae49 100644 --- a/ex_app/lib/all_tools/image_gen.py +++ b/ex_app/lib/all_tools/image_gen.py @@ -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 diff --git a/ex_app/lib/all_tools/lib/decorator.py b/ex_app/lib/all_tools/lib/decorator.py index c9c6ca4..d82ea8a 100644 --- a/ex_app/lib/all_tools/lib/decorator.py +++ b/ex_app/lib/all_tools/lib/decorator.py @@ -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 @@ -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 diff --git a/ex_app/lib/all_tools/mail.py b/ex_app/lib/all_tools/mail.py index fc03a45..aa81ace 100644 --- a/ex_app/lib/all_tools/mail.py +++ b/ex_app/lib/all_tools/mail.py @@ -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]): diff --git a/ex_app/lib/all_tools/mcp.py b/ex_app/lib/all_tools/mcp.py new file mode 100644 index 0000000..4d669af --- /dev/null +++ b/ex_app/lib/all_tools/mcp.py @@ -0,0 +1,35 @@ +# 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) + return await asyncio.wait_for(server.get_tools(), timeout=120) + 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 diff --git a/ex_app/lib/all_tools/openproject.py b/ex_app/lib/all_tools/openproject.py index c14ba5c..6efada6 100644 --- a/ex_app/lib/all_tools/openproject.py +++ b/ex_app/lib/all_tools/openproject.py @@ -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(): diff --git a/ex_app/lib/all_tools/openstreetmap.py b/ex_app/lib/all_tools/openstreetmap.py index 7e01131..4c910a3 100644 --- a/ex_app/lib/all_tools/openstreetmap.py +++ b/ex_app/lib/all_tools/openstreetmap.py @@ -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): diff --git a/ex_app/lib/all_tools/talk.py b/ex_app/lib/all_tools/talk.py index f833617..5c89551 100644 --- a/ex_app/lib/all_tools/talk.py +++ b/ex_app/lib/all_tools/talk.py @@ -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(): diff --git a/ex_app/lib/all_tools/weather.py b/ex_app/lib/all_tools/weather.py index 8df6728..7bc59fa 100644 --- a/ex_app/lib/all_tools/weather.py +++ b/ex_app/lib/all_tools/weather.py @@ -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]: diff --git a/ex_app/lib/all_tools/youtube.py b/ex_app/lib/all_tools/youtube.py index 41f30a3..cb4cab2 100644 --- a/ex_app/lib/all_tools/youtube.py +++ b/ex_app/lib/all_tools/youtube.py @@ -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 [ diff --git a/ex_app/lib/graph.py b/ex_app/lib/graph.py index b9de7a6..b90ab60 100644 --- a/ex_app/lib/graph.py +++ b/ex_app/lib/graph.py @@ -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} diff --git a/ex_app/lib/main.py b/ex_app/lib/main.py index 50c88d1..c438aa6 100644 --- a/ex_app/lib/main.py +++ b/ex_app/lib/main.py @@ -4,17 +4,17 @@ import traceback from contextlib import asynccontextmanager from json import JSONDecodeError -from threading import Thread, Event -from time import sleep +from threading import Event +import asyncio import httpx import json from fastapi import FastAPI from nc_py_api import NextcloudApp, NextcloudException from nc_py_api.ex_app import ( - AppAPIAuthMiddleware, - LogLvl, - run_app, + AppAPIAuthMiddleware, + LogLvl, + run_app, set_handlers, SettingsForm, SettingsField, @@ -65,7 +65,7 @@ async def lifespan(app: FastAPI): type=SettingsFieldType.MULTI_CHECKBOX, default=dict.fromkeys(categories, True), options={v: k for k, v in categories.items()}, - ), + ), SettingsField( id="here_api", title=_("API Key HERE"), @@ -74,7 +74,15 @@ async def lifespan(app: FastAPI): default="", placeholder=_("API key"), ), - ] + SettingsField( + id="mcp_config", + title=_("MCP Config"), + description=_("JSON configuration for the MCP. For an example look at https://langchain-ai.github.io/langgraph/agents/mcp/ but make sure to only use http transport MCPs."), + type=SettingsFieldType.TEXT, + default="", + placeholder="{\"weather\": {\"url\": \"https://weather.internet/mcp\",\"transport\": \"streamable_http\"}}", + ), + ] ) @@ -102,23 +110,23 @@ def enabled_handler(enabled: bool, nc: NextcloudApp) -> str: return "" -def background_thread_task(): +async def background_thread_task(): nc = NextcloudApp() while True: if not app_enabled.is_set(): - sleep(5) + await asyncio.sleep(5) continue try: response = nc.providers.task_processing.next_task([provider.id], [provider.task_type]) if not response or not 'task' in response: - sleep(2) + await asyncio.sleep(2) continue except (NextcloudException, httpx.RequestError, JSONDecodeError) as e: tb_str = ''.join(traceback.format_exception(e)) log(nc, LogLvl.WARNING, "Error fetching the next task " + tb_str) - sleep(5) + await asyncio.sleep(5) continue except ( httpx.RemoteProtocolError, @@ -127,44 +135,46 @@ def background_thread_task(): httpx.PoolTimeout, ) as e: log(nc, LogLvl.DEBUG, "Ignored error during task polling") - sleep(2) + await asyncio.sleep(2) continue task = response["task"] log(nc, LogLvl.INFO, 'New Task incoming') log(nc, LogLvl.DEBUG, str(task)) log(nc, LogLvl.INFO, str({'input': task['input']['input'], 'confirmation': task['input']['confirmation'], 'conversation_token': ''})) - + asyncio.create_task(handle_task(task, nc)) + await asyncio.sleep(5) + + +async def handle_task(task, nc: NextcloudApp): + try: + nextcloud = NextcloudApp() + if task['userId']: + nextcloud.set_user(task['userId']) + output = await react(task, nextcloud) + except Exception as e: # noqa + tb_str = ''.join(traceback.format_exception(e)) + log(nc, LogLvl.ERROR, "Error: " + tb_str) try: - nextcloud = NextcloudApp() - if task['userId']: - nextcloud.set_user(task['userId']) - output = react(task, nextcloud) - except Exception as e: # noqa - tb_str = ''.join(traceback.format_exception(e)) - log(nc, LogLvl.ERROR,"Error: " + tb_str) - try: - nc.providers.task_processing.report_result(task["id"], error_message=str(e)) - except (NextcloudException, httpx.RequestError) as net_err: - tb_str = ''.join(traceback.format_exception(net_err)) - log(nc, LogLvl.WARNING, "Network error in reporting the error: " + tb_str) - sleep(5) - continue - try: - NextcloudApp().providers.task_processing.report_result( - task["id"], - output, - ) - except (NextcloudException, httpx.RequestError, JSONDecodeError) as e: - tb_str = ''.join(traceback.format_exception(e)) - log(nc, LogLvl.ERROR,"Network error trying to report the task result: " + tb_str) - sleep(5) + nc.providers.task_processing.report_result(task["id"], error_message=str(e)) + except (NextcloudException, httpx.RequestError) as net_err: + tb_str = ''.join(traceback.format_exception(net_err)) + log(nc, LogLvl.WARNING, "Network error in reporting the error: " + tb_str) + return + try: + NextcloudApp().providers.task_processing.report_result( + task["id"], + output, + ) + except (NextcloudException, httpx.RequestError, JSONDecodeError) as e: + tb_str = ''.join(traceback.format_exception(e)) + log(nc, LogLvl.ERROR, "Network error trying to report the task result: " + tb_str) def start_bg_task(): - t = Thread(target=background_thread_task, args=()) - t.start() + loop = asyncio.get_event_loop() + loop.create_task(background_thread_task()) if __name__ == "__main__": # Wrapper around `uvicorn.run`. diff --git a/ex_app/lib/tools.py b/ex_app/lib/tools.py index eef8d23..6452268 100644 --- a/ex_app/lib/tools.py +++ b/ex_app/lib/tools.py @@ -6,11 +6,12 @@ import json from os.path import dirname +from langchain_mcp_adapters.client import MultiServerMCPClient from nc_py_api import Nextcloud from ex_app.lib.all_tools.lib.decorator import timed_memoize @timed_memoize(1*60) -def get_tools(nc: Nextcloud): +async def get_tools(nc: Nextcloud): directory = dirname(__file__) + '/all_tools' function_name = "get_tools" @@ -36,9 +37,9 @@ def get_tools(nc: Nextcloud): continue if callable(get_tools_from_import): print(f"Invoking {function_name} from {module_name}") - imported_tools = get_tools_from_import(nc) + imported_tools = await get_tools_from_import(nc) for tool in imported_tools: - if not hasattr(tool, 'func'): + if not hasattr(tool, 'func') or not hasattr(tool.func, 'safe'): safe_tools.append(tool) # external tools cannot be decorated and should always be safe continue if not tool.func.safe: diff --git a/poetry.lock b/poetry.lock index 71970e9..d8d0316 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -193,7 +193,7 @@ description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, @@ -509,7 +509,7 @@ description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -874,6 +874,43 @@ files = [ {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, ] +[[package]] +name = "jsonschema" +version = "4.25.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "jsonschema-4.25.0-py3-none-any.whl", hash = "sha256:24c2e8da302de79c8b9382fee3e76b355e44d2a4364bb207159ce10b517bd716"}, + {file = "jsonschema-4.25.0.tar.gz", hash = "sha256:e63acf5c11762c0e6672ffb61482bdf57f0876684d8d249c0fe2d730d48bc55f"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "rfc3987-syntax (>=1.1.0)", "uri-template", "webcolors (>=24.6.0)"] + +[[package]] +name = "jsonschema-specifications" +version = "2025.4.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af"}, + {file = "jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608"}, +] + +[package.dependencies] +referencing = ">=0.31.0" + [[package]] name = "langchain" version = "0.3.25" @@ -968,6 +1005,23 @@ PyYAML = ">=5.3" tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" typing-extensions = ">=4.7" +[[package]] +name = "langchain-mcp-adapters" +version = "0.1.9" +description = "Make Anthropic Model Context Protocol (MCP) tools compatible with LangChain and LangGraph agents." +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "langchain_mcp_adapters-0.1.9-py3-none-any.whl", hash = "sha256:fd131009c60c9e5a864f96576bbe757fc1809abd604891cb2e5d6e8aebd6975c"}, + {file = "langchain_mcp_adapters-0.1.9.tar.gz", hash = "sha256:0018cf7b5f7bc4c044e05ec20fcb9ebe345311c8d1060c61d411188001ab3aab"}, +] + +[package.dependencies] +langchain-core = ">=0.3.36,<0.4" +mcp = ">=1.9.2" +typing-extensions = ">=4.14.0" + [[package]] name = "langchain-text-splitters" version = "0.3.8" @@ -1250,6 +1304,36 @@ dev = ["marshmallow[tests]", "pre-commit (>=3.5,<5.0)", "tox"] docs = ["autodocsumm (==0.2.14)", "furo (==2024.8.6)", "sphinx (==8.1.3)", "sphinx-copybutton (==0.5.2)", "sphinx-issues (==5.0.0)", "sphinxext-opengraph (==0.9.1)"] tests = ["pytest", "simplejson"] +[[package]] +name = "mcp" +version = "1.12.2" +description = "Model Context Protocol SDK" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "mcp-1.12.2-py3-none-any.whl", hash = "sha256:b86d584bb60193a42bd78aef01882c5c42d614e416cbf0480149839377ab5a5f"}, + {file = "mcp-1.12.2.tar.gz", hash = "sha256:a4b7c742c50ce6ed6d6a6c096cca0e3893f5aecc89a59ed06d47c4e6ba41edcc"}, +] + +[package.dependencies] +anyio = ">=4.5" +httpx = ">=0.27" +httpx-sse = ">=0.4" +jsonschema = ">=4.20.0" +pydantic = ">=2.8.0,<3.0.0" +pydantic-settings = ">=2.5.2" +python-multipart = ">=0.0.9" +pywin32 = {version = ">=310", markers = "sys_platform == \"win32\""} +sse-starlette = ">=1.6.1" +starlette = ">=0.27" +uvicorn = {version = ">=0.23.1", markers = "sys_platform != \"emscripten\""} + +[package.extras] +cli = ["python-dotenv (>=1.0.0)", "typer (>=0.16.0)"] +rich = ["rich (>=13.9.4)"] +ws = ["websockets (>=15.0.1)"] + [[package]] name = "multidict" version = "6.4.3" @@ -1480,7 +1564,6 @@ description = "Fast, correct Python JSON library supporting dataclasses, datetim optional = false python-versions = ">=3.9" groups = ["main"] -markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "orjson-3.10.18-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a45e5d68066b408e4bc383b6e4ef05e717c65219a9e1390abc6155a520cac402"}, {file = "orjson-3.10.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be3b9b143e8b9db05368b13b04c84d37544ec85bb97237b3a923f076265ec89c"}, @@ -1950,6 +2033,18 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "python-multipart" +version = "0.0.20" +description = "A streaming multipart parser for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104"}, + {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, +] + [[package]] name = "pytz" version = "2025.2" @@ -1962,6 +2057,37 @@ files = [ {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, ] +[[package]] +name = "pywin32" +version = "311" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +groups = ["main"] +markers = "sys_platform == \"win32\"" +files = [ + {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"}, + {file = "pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b"}, + {file = "pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b"}, + {file = "pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151"}, + {file = "pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503"}, + {file = "pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2"}, + {file = "pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31"}, + {file = "pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067"}, + {file = "pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852"}, + {file = "pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d"}, + {file = "pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d"}, + {file = "pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a"}, + {file = "pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee"}, + {file = "pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87"}, + {file = "pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42"}, + {file = "pywin32-311-cp38-cp38-win32.whl", hash = "sha256:6c6f2969607b5023b0d9ce2541f8d2cbb01c4f46bc87456017cf63b73f1e2d8c"}, + {file = "pywin32-311-cp38-cp38-win_amd64.whl", hash = "sha256:c8015b09fb9a5e188f83b7b04de91ddca4658cee2ae6f3bc483f0b21a77ef6cd"}, + {file = "pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b"}, + {file = "pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91"}, + {file = "pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d"}, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -2046,6 +2172,23 @@ x-wr-timezone = {version = ">=1.0.0,<3.0.0", markers = "python_version >= \"3.9\ [package.extras] test = ["pygments", "pytest", "pytest-cov", "pytz (>=2023.3)", "restructuredtext-lint"] +[[package]] +name = "referencing" +version = "0.36.2" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"}, + {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" +typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} + [[package]] name = "requests" version = "2.32.4" @@ -2083,6 +2226,160 @@ files = [ [package.dependencies] requests = ">=2.0.1,<3.0.0" +[[package]] +name = "rpds-py" +version = "0.26.0" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "rpds_py-0.26.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4c70c70f9169692b36307a95f3d8c0a9fcd79f7b4a383aad5eaa0e9718b79b37"}, + {file = "rpds_py-0.26.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:777c62479d12395bfb932944e61e915741e364c843afc3196b694db3d669fcd0"}, + {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec671691e72dff75817386aa02d81e708b5a7ec0dec6669ec05213ff6b77e1bd"}, + {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a1cb5d6ce81379401bbb7f6dbe3d56de537fb8235979843f0d53bc2e9815a79"}, + {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f789e32fa1fb6a7bf890e0124e7b42d1e60d28ebff57fe806719abb75f0e9a3"}, + {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c55b0a669976cf258afd718de3d9ad1b7d1fe0a91cd1ab36f38b03d4d4aeaaf"}, + {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70d9ec912802ecfd6cd390dadb34a9578b04f9bcb8e863d0a7598ba5e9e7ccc"}, + {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3021933c2cb7def39d927b9862292e0f4c75a13d7de70eb0ab06efed4c508c19"}, + {file = "rpds_py-0.26.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a7898b6ca3b7d6659e55cdac825a2e58c638cbf335cde41f4619e290dd0ad11"}, + {file = "rpds_py-0.26.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:12bff2ad9447188377f1b2794772f91fe68bb4bbfa5a39d7941fbebdbf8c500f"}, + {file = "rpds_py-0.26.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:191aa858f7d4902e975d4cf2f2d9243816c91e9605070aeb09c0a800d187e323"}, + {file = "rpds_py-0.26.0-cp310-cp310-win32.whl", hash = "sha256:b37a04d9f52cb76b6b78f35109b513f6519efb481d8ca4c321f6a3b9580b3f45"}, + {file = "rpds_py-0.26.0-cp310-cp310-win_amd64.whl", hash = "sha256:38721d4c9edd3eb6670437d8d5e2070063f305bfa2d5aa4278c51cedcd508a84"}, + {file = "rpds_py-0.26.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9e8cb77286025bdb21be2941d64ac6ca016130bfdcd228739e8ab137eb4406ed"}, + {file = "rpds_py-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e09330b21d98adc8ccb2dbb9fc6cb434e8908d4c119aeaa772cb1caab5440a0"}, + {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9c1b92b774b2e68d11193dc39620d62fd8ab33f0a3c77ecdabe19c179cdbc1"}, + {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:824e6d3503ab990d7090768e4dfd9e840837bae057f212ff9f4f05ec6d1975e7"}, + {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ad7fd2258228bf288f2331f0a6148ad0186b2e3643055ed0db30990e59817a6"}, + {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dc23bbb3e06ec1ea72d515fb572c1fea59695aefbffb106501138762e1e915e"}, + {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80bf832ac7b1920ee29a426cdca335f96a2b5caa839811803e999b41ba9030d"}, + {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0919f38f5542c0a87e7b4afcafab6fd2c15386632d249e9a087498571250abe3"}, + {file = "rpds_py-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d422b945683e409000c888e384546dbab9009bb92f7c0b456e217988cf316107"}, + {file = "rpds_py-0.26.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a7711fa562ba2da1aa757e11024ad6d93bad6ad7ede5afb9af144623e5f76a"}, + {file = "rpds_py-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238e8c8610cb7c29460e37184f6799547f7e09e6a9bdbdab4e8edb90986a2318"}, + {file = "rpds_py-0.26.0-cp311-cp311-win32.whl", hash = "sha256:893b022bfbdf26d7bedb083efeea624e8550ca6eb98bf7fea30211ce95b9201a"}, + {file = "rpds_py-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:87a5531de9f71aceb8af041d72fc4cab4943648d91875ed56d2e629bef6d4c03"}, + {file = "rpds_py-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:de2713f48c1ad57f89ac25b3cb7daed2156d8e822cf0eca9b96a6f990718cc41"}, + {file = "rpds_py-0.26.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:894514d47e012e794f1350f076c427d2347ebf82f9b958d554d12819849a369d"}, + {file = "rpds_py-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc921b96fa95a097add244da36a1d9e4f3039160d1d30f1b35837bf108c21136"}, + {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e1157659470aa42a75448b6e943c895be8c70531c43cb78b9ba990778955582"}, + {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:521ccf56f45bb3a791182dc6b88ae5f8fa079dd705ee42138c76deb1238e554e"}, + {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9def736773fd56b305c0eef698be5192c77bfa30d55a0e5885f80126c4831a15"}, + {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdad4ea3b4513b475e027be79e5a0ceac8ee1c113a1a11e5edc3c30c29f964d8"}, + {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82b165b07f416bdccf5c84546a484cc8f15137ca38325403864bfdf2b5b72f6a"}, + {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d04cab0a54b9dba4d278fe955a1390da3cf71f57feb78ddc7cb67cbe0bd30323"}, + {file = "rpds_py-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:79061ba1a11b6a12743a2b0f72a46aa2758613d454aa6ba4f5a265cc48850158"}, + {file = "rpds_py-0.26.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f405c93675d8d4c5ac87364bb38d06c988e11028a64b52a47158a355079661f3"}, + {file = "rpds_py-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dafd4c44b74aa4bed4b250f1aed165b8ef5de743bcca3b88fc9619b6087093d2"}, + {file = "rpds_py-0.26.0-cp312-cp312-win32.whl", hash = "sha256:3da5852aad63fa0c6f836f3359647870e21ea96cf433eb393ffa45263a170d44"}, + {file = "rpds_py-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf47cfdabc2194a669dcf7a8dbba62e37a04c5041d2125fae0233b720da6f05c"}, + {file = "rpds_py-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:20ab1ae4fa534f73647aad289003f1104092890849e0266271351922ed5574f8"}, + {file = "rpds_py-0.26.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:696764a5be111b036256c0b18cd29783fab22154690fc698062fc1b0084b511d"}, + {file = "rpds_py-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6c15d2080a63aaed876e228efe4f814bc7889c63b1e112ad46fdc8b368b9e1"}, + {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390e3170babf42462739a93321e657444f0862c6d722a291accc46f9d21ed04e"}, + {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7da84c2c74c0f5bc97d853d9e17bb83e2dcafcff0dc48286916001cc114379a1"}, + {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c5fe114a6dd480a510b6d3661d09d67d1622c4bf20660a474507aaee7eeeee9"}, + {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3100b3090269f3a7ea727b06a6080d4eb7439dca4c0e91a07c5d133bb1727ea7"}, + {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c03c9b0c64afd0320ae57de4c982801271c0c211aa2d37f3003ff5feb75bb04"}, + {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5963b72ccd199ade6ee493723d18a3f21ba7d5b957017607f815788cef50eaf1"}, + {file = "rpds_py-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9da4e873860ad5bab3291438525cae80169daecbfafe5657f7f5fb4d6b3f96b9"}, + {file = "rpds_py-0.26.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5afaddaa8e8c7f1f7b4c5c725c0070b6eed0228f705b90a1732a48e84350f4e9"}, + {file = "rpds_py-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4916dc96489616a6f9667e7526af8fa693c0fdb4f3acb0e5d9f4400eb06a47ba"}, + {file = "rpds_py-0.26.0-cp313-cp313-win32.whl", hash = "sha256:2a343f91b17097c546b93f7999976fd6c9d5900617aa848c81d794e062ab302b"}, + {file = "rpds_py-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:0a0b60701f2300c81b2ac88a5fb893ccfa408e1c4a555a77f908a2596eb875a5"}, + {file = "rpds_py-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:257d011919f133a4746958257f2c75238e3ff54255acd5e3e11f3ff41fd14256"}, + {file = "rpds_py-0.26.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:529c8156d7506fba5740e05da8795688f87119cce330c244519cf706a4a3d618"}, + {file = "rpds_py-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f53ec51f9d24e9638a40cabb95078ade8c99251945dad8d57bf4aabe86ecee35"}, + {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab504c4d654e4a29558eaa5bb8cea5fdc1703ea60a8099ffd9c758472cf913f"}, + {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd0641abca296bc1a00183fe44f7fced8807ed49d501f188faa642d0e4975b83"}, + {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b312fecc1d017b5327afa81d4da1480f51c68810963a7336d92203dbb3d4f1"}, + {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c741107203954f6fc34d3066d213d0a0c40f7bb5aafd698fb39888af277c70d8"}, + {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3e55a7db08dc9a6ed5fb7103019d2c1a38a349ac41901f9f66d7f95750942f"}, + {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e851920caab2dbcae311fd28f4313c6953993893eb5c1bb367ec69d9a39e7ed"}, + {file = "rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dfbf280da5f876d0b00c81f26bedce274e72a678c28845453885a9b3c22ae632"}, + {file = "rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1cc81d14ddfa53d7f3906694d35d54d9d3f850ef8e4e99ee68bc0d1e5fed9a9c"}, + {file = "rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dca83c498b4650a91efcf7b88d669b170256bf8017a5db6f3e06c2bf031f57e0"}, + {file = "rpds_py-0.26.0-cp313-cp313t-win32.whl", hash = "sha256:4d11382bcaf12f80b51d790dee295c56a159633a8e81e6323b16e55d81ae37e9"}, + {file = "rpds_py-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff110acded3c22c033e637dd8896e411c7d3a11289b2edf041f86663dbc791e9"}, + {file = "rpds_py-0.26.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:da619979df60a940cd434084355c514c25cf8eb4cf9a508510682f6c851a4f7a"}, + {file = "rpds_py-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ea89a2458a1a75f87caabefe789c87539ea4e43b40f18cff526052e35bbb4fdf"}, + {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feac1045b3327a45944e7dcbeb57530339f6b17baff154df51ef8b0da34c8c12"}, + {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b818a592bd69bfe437ee8368603d4a2d928c34cffcdf77c2e761a759ffd17d20"}, + {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a8b0dd8648709b62d9372fc00a57466f5fdeefed666afe3fea5a6c9539a0331"}, + {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d3498ad0df07d81112aa6ec6c95a7e7b1ae00929fb73e7ebee0f3faaeabad2f"}, + {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24a4146ccb15be237fdef10f331c568e1b0e505f8c8c9ed5d67759dac58ac246"}, + {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a9a63785467b2d73635957d32a4f6e73d5e4df497a16a6392fa066b753e87387"}, + {file = "rpds_py-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:de4ed93a8c91debfd5a047be327b7cc8b0cc6afe32a716bbbc4aedca9e2a83af"}, + {file = "rpds_py-0.26.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:caf51943715b12af827696ec395bfa68f090a4c1a1d2509eb4e2cb69abbbdb33"}, + {file = "rpds_py-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4a59e5bc386de021f56337f757301b337d7ab58baa40174fb150accd480bc953"}, + {file = "rpds_py-0.26.0-cp314-cp314-win32.whl", hash = "sha256:92c8db839367ef16a662478f0a2fe13e15f2227da3c1430a782ad0f6ee009ec9"}, + {file = "rpds_py-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:b0afb8cdd034150d4d9f53926226ed27ad15b7f465e93d7468caaf5eafae0d37"}, + {file = "rpds_py-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:ca3f059f4ba485d90c8dc75cb5ca897e15325e4e609812ce57f896607c1c0867"}, + {file = "rpds_py-0.26.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5afea17ab3a126006dc2f293b14ffc7ef3c85336cf451564a0515ed7648033da"}, + {file = "rpds_py-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:69f0c0a3df7fd3a7eec50a00396104bb9a843ea6d45fcc31c2d5243446ffd7a7"}, + {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:801a71f70f9813e82d2513c9a96532551fce1e278ec0c64610992c49c04c2dad"}, + {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df52098cde6d5e02fa75c1f6244f07971773adb4a26625edd5c18fee906fa84d"}, + {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bc596b30f86dc6f0929499c9e574601679d0341a0108c25b9b358a042f51bca"}, + {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dfbe56b299cf5875b68eb6f0ebaadc9cac520a1989cac0db0765abfb3709c19"}, + {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac64f4b2bdb4ea622175c9ab7cf09444e412e22c0e02e906978b3b488af5fde8"}, + {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:181ef9b6bbf9845a264f9aa45c31836e9f3c1f13be565d0d010e964c661d1e2b"}, + {file = "rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:49028aa684c144ea502a8e847d23aed5e4c2ef7cadfa7d5eaafcb40864844b7a"}, + {file = "rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e5d524d68a474a9688336045bbf76cb0def88549c1b2ad9dbfec1fb7cfbe9170"}, + {file = "rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1851f429b822831bd2edcbe0cfd12ee9ea77868f8d3daf267b189371671c80e"}, + {file = "rpds_py-0.26.0-cp314-cp314t-win32.whl", hash = "sha256:7bdb17009696214c3b66bb3590c6d62e14ac5935e53e929bcdbc5a495987a84f"}, + {file = "rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7"}, + {file = "rpds_py-0.26.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:7a48af25d9b3c15684059d0d1fc0bc30e8eee5ca521030e2bffddcab5be40226"}, + {file = "rpds_py-0.26.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0c71c2f6bf36e61ee5c47b2b9b5d47e4d1baad6426bfed9eea3e858fc6ee8806"}, + {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d815d48b1804ed7867b539236b6dd62997850ca1c91cad187f2ddb1b7bbef19"}, + {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84cfbd4d4d2cdeb2be61a057a258d26b22877266dd905809e94172dff01a42ae"}, + {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbaa70553ca116c77717f513e08815aec458e6b69a028d4028d403b3bc84ff37"}, + {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39bfea47c375f379d8e87ab4bb9eb2c836e4f2069f0f65731d85e55d74666387"}, + {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1533b7eb683fb5f38c1d68a3c78f5fdd8f1412fa6b9bf03b40f450785a0ab915"}, + {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c5ab0ee51f560d179b057555b4f601b7df909ed31312d301b99f8b9fc6028284"}, + {file = "rpds_py-0.26.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e5162afc9e0d1f9cae3b577d9c29ddbab3505ab39012cb794d94a005825bde21"}, + {file = "rpds_py-0.26.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:43f10b007033f359bc3fa9cd5e6c1e76723f056ffa9a6b5c117cc35720a80292"}, + {file = "rpds_py-0.26.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e3730a48e5622e598293eee0762b09cff34dd3f271530f47b0894891281f051d"}, + {file = "rpds_py-0.26.0-cp39-cp39-win32.whl", hash = "sha256:4b1f66eb81eab2e0ff5775a3a312e5e2e16bf758f7b06be82fb0d04078c7ac51"}, + {file = "rpds_py-0.26.0-cp39-cp39-win_amd64.whl", hash = "sha256:519067e29f67b5c90e64fb1a6b6e9d2ec0ba28705c51956637bac23a2f4ddae1"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3c0909c5234543ada2515c05dc08595b08d621ba919629e94427e8e03539c958"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c1fb0cda2abcc0ac62f64e2ea4b4e64c57dfd6b885e693095460c61bde7bb18e"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84d142d2d6cf9b31c12aa4878d82ed3b2324226270b89b676ac62ccd7df52d08"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a547e21c5610b7e9093d870be50682a6a6cf180d6da0f42c47c306073bfdbbf6"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35e9a70a0f335371275cdcd08bc5b8051ac494dd58bff3bbfb421038220dc871"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dfa6115c6def37905344d56fb54c03afc49104e2ca473d5dedec0f6606913b4"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:313cfcd6af1a55a286a3c9a25f64af6d0e46cf60bc5798f1db152d97a216ff6f"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f7bf2496fa563c046d05e4d232d7b7fd61346e2402052064b773e5c378bf6f73"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:aa81873e2c8c5aa616ab8e017a481a96742fdf9313c40f14338ca7dbf50cb55f"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:68ffcf982715f5b5b7686bdd349ff75d422e8f22551000c24b30eaa1b7f7ae84"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6188de70e190847bb6db3dc3981cbadff87d27d6fe9b4f0e18726d55795cee9b"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1c962145c7473723df9722ba4c058de12eb5ebedcb4e27e7d902920aa3831ee8"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f61a9326f80ca59214d1cceb0a09bb2ece5b2563d4e0cd37bfd5515c28510674"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:183f857a53bcf4b1b42ef0f57ca553ab56bdd170e49d8091e96c51c3d69ca696"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:941c1cfdf4799d623cf3aa1d326a6b4fdb7a5799ee2687f3516738216d2262fb"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72a8d9564a717ee291f554eeb4bfeafe2309d5ec0aa6c475170bdab0f9ee8e88"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:511d15193cbe013619dd05414c35a7dedf2088fcee93c6bbb7c77859765bd4e8"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aea1f9741b603a8d8fedb0ed5502c2bc0accbc51f43e2ad1337fe7259c2b77a5"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4019a9d473c708cf2f16415688ef0b4639e07abaa569d72f74745bbeffafa2c7"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:093d63b4b0f52d98ebae33b8c50900d3d67e0666094b1be7a12fffd7f65de74b"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2abe21d8ba64cded53a2a677e149ceb76dcf44284202d737178afe7ba540c1eb"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:4feb7511c29f8442cbbc28149a92093d32e815a28aa2c50d333826ad2a20fdf0"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e99685fc95d386da368013e7fb4269dd39c30d99f812a8372d62f244f662709c"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a90a13408a7a856b87be8a9f008fff53c5080eea4e4180f6c2e546e4a972fb5d"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3ac51b65e8dc76cf4949419c54c5528adb24fc721df722fd452e5fbc236f5c40"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59b2093224a18c6508d95cfdeba8db9cbfd6f3494e94793b58972933fcee4c6d"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f01a5d6444a3258b00dc07b6ea4733e26f8072b788bef750baa37b370266137"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6e2c12160c72aeda9d1283e612f68804621f448145a210f1bf1d79151c47090"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb28c1f569f8d33b2b5dcd05d0e6ef7005d8639c54c2f0be824f05aedf715255"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1766b5724c3f779317d5321664a343c07773c8c5fd1532e4039e6cc7d1a815be"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b6d9e5a2ed9c4988c8f9b28b3bc0e3e5b1aaa10c28d210a594ff3a8c02742daf"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:b5f7a446ddaf6ca0fad9a5535b56fbfc29998bf0e0b450d174bbec0d600e1d72"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:eed5ac260dd545fbc20da5f4f15e7efe36a55e0e7cf706e4ec005b491a9546a0"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:582462833ba7cee52e968b0341b85e392ae53d44c0f9af6a5927c80e539a8b67"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:69a607203441e07e9a8a529cff1d5b73f6a160f22db1097211e6212a68567d11"}, + {file = "rpds_py-0.26.0.tar.gz", hash = "sha256:20dae58a859b0906f0685642e591056f1e787f3a8b39c8e8749a45dc7d26bdb0"}, +] + [[package]] name = "six" version = "1.17.0" @@ -2203,6 +2500,27 @@ postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] pymysql = ["pymysql"] sqlcipher = ["sqlcipher3_binary"] +[[package]] +name = "sse-starlette" +version = "2.4.1" +description = "SSE plugin for Starlette" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "sse_starlette-2.4.1-py3-none-any.whl", hash = "sha256:08b77ea898ab1a13a428b2b6f73cfe6d0e607a7b4e15b9bb23e4a37b087fd39a"}, + {file = "sse_starlette-2.4.1.tar.gz", hash = "sha256:7c8a800a1ca343e9165fc06bbda45c78e4c6166320707ae30b416c42da070926"}, +] + +[package.dependencies] +anyio = ">=4.7.0" + +[package.extras] +daphne = ["daphne (>=4.2.0)"] +examples = ["aiosqlite (>=0.21.0)", "fastapi (>=0.115.12)", "sqlalchemy[asyncio,examples] (>=2.0.41)", "starlette (>=0.41.3)", "uvicorn (>=0.34.0)"] +granian = ["granian (>=2.3.1)"] +uvicorn = ["uvicorn (>=0.34.0)"] + [[package]] name = "starlette" version = "0.46.2" @@ -2279,14 +2597,14 @@ files = [ [[package]] name = "typing-extensions" -version = "4.13.2" -description = "Backported and Experimental Type Hints for Python 3.8+" +version = "4.14.1" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, - {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, ] [[package]] @@ -2817,4 +3135,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "05ccd3694e7d62881ca13780198016354c5931353c6cef6de6e5b2459a2263b9" +content-hash = "08c050f77b81d1297a6f1d2bb9dbcaf4b83e0bb8ce9aa9ed63b03a548b0c6436" diff --git a/pyproject.toml b/pyproject.toml index 129b783..73a5c51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ vobject = "^0.9.9" youtube-search = "^2.1.2" duckduckgo-search = "^8.0.1" uvicorn = "^0.34.2" +langchain-mcp-adapters = "^0.1.9" [build-system] requires = ["poetry-core"] From 5f2057fd945acbe56af7e0d4c555491a748a2d60 Mon Sep 17 00:00:00 2001 From: Lukas Schaefer Date: Tue, 29 Jul 2025 11:42:28 -0400 Subject: [PATCH 2/4] small mistake Signed-off-by: Lukas Schaefer --- ex_app/lib/all_tools/mcp.py | 2 +- ex_app/lib/main.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ex_app/lib/all_tools/mcp.py b/ex_app/lib/all_tools/mcp.py index 4d669af..917f53c 100644 --- a/ex_app/lib/all_tools/mcp.py +++ b/ex_app/lib/all_tools/mcp.py @@ -12,7 +12,7 @@ async def get_tools(nc: Nextcloud): - mcp_json = nc.appconfig_ex.get_value("mcp_config", {}) + mcp_json = nc.appconfig_ex.get_value("mcp_config", "{}") try: mcp_config = json.loads(mcp_json) except JSONDecodeError: diff --git a/ex_app/lib/main.py b/ex_app/lib/main.py index c438aa6..f4dd809 100644 --- a/ex_app/lib/main.py +++ b/ex_app/lib/main.py @@ -77,7 +77,7 @@ async def lifespan(app: FastAPI): SettingsField( id="mcp_config", title=_("MCP Config"), - description=_("JSON configuration for the MCP. For an example look at https://langchain-ai.github.io/langgraph/agents/mcp/ but make sure to only use http transport MCPs."), + description=_("JSON configuration for the MCP. For an example look at https://langchain-ai.github.io/langgraph/agents/mcp/ but make sure to only use streamable_http MCPs."), type=SettingsFieldType.TEXT, default="", placeholder="{\"weather\": {\"url\": \"https://weather.internet/mcp\",\"transport\": \"streamable_http\"}}", From b2209a22d59d9a16f619ee899a0c17bf52cc6b2c Mon Sep 17 00:00:00 2001 From: Lukas Schaefer Date: Tue, 29 Jul 2025 14:26:28 -0400 Subject: [PATCH 3/4] mark mcp tools as not safe Signed-off-by: Lukas Schaefer --- ex_app/lib/tools.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ex_app/lib/tools.py b/ex_app/lib/tools.py index 6452268..9ea92c1 100644 --- a/ex_app/lib/tools.py +++ b/ex_app/lib/tools.py @@ -39,11 +39,11 @@ async def get_tools(nc: Nextcloud): print(f"Invoking {function_name} from {module_name}") imported_tools = await get_tools_from_import(nc) for tool in imported_tools: - if not hasattr(tool, 'func') or not hasattr(tool.func, 'safe'): - safe_tools.append(tool) # external tools cannot be decorated and should always be safe + if not hasattr(tool, 'func'): + safe_tools.append(tool) continue - if not tool.func.safe: - dangerous_tools.append(tool) + if not hasattr(tool.func, 'safe') or not tool.func.safe: + dangerous_tools.append(tool) # MCP tools cannot be decorated and should always be dangerous else: safe_tools.append(tool) else: From 8c09b4fe7f32f4f0135b05fcb796b3da45b75aea Mon Sep 17 00:00:00 2001 From: Lukas Schaefer Date: Thu, 31 Jul 2025 08:22:30 -0400 Subject: [PATCH 4/4] add mcp before tools and improve description Signed-off-by: Lukas Schaefer --- ex_app/lib/all_tools/mcp.py | 5 ++++- ex_app/lib/main.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ex_app/lib/all_tools/mcp.py b/ex_app/lib/all_tools/mcp.py index 917f53c..65dd35c 100644 --- a/ex_app/lib/all_tools/mcp.py +++ b/ex_app/lib/all_tools/mcp.py @@ -20,7 +20,10 @@ async def get_tools(nc: Nextcloud): mcp_config = {} try: server = MultiServerMCPClient(mcp_config) - return await asyncio.wait_for(server.get_tools(), timeout=120) + 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) diff --git a/ex_app/lib/main.py b/ex_app/lib/main.py index f4dd809..363faea 100644 --- a/ex_app/lib/main.py +++ b/ex_app/lib/main.py @@ -77,7 +77,7 @@ async def lifespan(app: FastAPI): SettingsField( id="mcp_config", title=_("MCP Config"), - description=_("JSON configuration for the MCP. For an example look at https://langchain-ai.github.io/langgraph/agents/mcp/ but make sure to only use streamable_http MCPs."), + description=_("JSON configuration for the MCP. Structured as {\"service_name\": {\"url\": \"https://service.url\",\"transport\": \"streamable_http\"}}. For more details view the documentation for context_agent."), type=SettingsFieldType.TEXT, default="", placeholder="{\"weather\": {\"url\": \"https://weather.internet/mcp\",\"transport\": \"streamable_http\"}}",