mirror of
https://github.com/langchain-ai/oap-langgraph-tools-agent.git
synced 2026-07-01 20:54:00 -04:00
fix: Drop LC MCP adapters pkg
This commit is contained in:
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"python.languageServer": "None",
|
||||
"makefile.configureOnOpen": false
|
||||
}
|
||||
+3
-3
@@ -13,9 +13,9 @@ dependencies = [
|
||||
"langchain-openai==0.3.16",
|
||||
"pydantic==2.11.3",
|
||||
"langchain==0.3.25",
|
||||
"langchain-mcp-adapters==0.0.11",
|
||||
"supabase>=2.15.1",
|
||||
"aiohttp>=3.8.0",
|
||||
"mcp>=1.9.0",
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
@@ -24,7 +24,7 @@ packages = ["tools_agent"]
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"ruff>=0.8.4",
|
||||
"langgraph-api==0.2.21",
|
||||
"langgraph-api==0.2.27",
|
||||
"langgraph-cli==0.2.10",
|
||||
"langgraph-runtime-inmem>=0.0.11",
|
||||
]
|
||||
]
|
||||
|
||||
+69
-41
@@ -4,10 +4,12 @@ from pydantic import BaseModel, Field
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from tools_agent.utils.tools import create_rag_tool
|
||||
from langchain.chat_models import init_chat_model
|
||||
from langchain_mcp_adapters.client import MultiServerMCPClient
|
||||
from mcp.client.streamable_http import streamablehttp_client
|
||||
from mcp import ClientSession
|
||||
import mcp.types as types
|
||||
from tools_agent.utils.token import fetch_tokens
|
||||
from contextlib import asynccontextmanager
|
||||
from tools_agent.utils.tools import wrap_mcp_authenticate_tool
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
UNEDITABLE_SYSTEM_PROMPT = "\nIf the tool throws an error requiring authentication, provide the user with a Markdown link to the authentication page and prompt them to authenticate."
|
||||
|
||||
@@ -128,48 +130,74 @@ class GraphConfigPydantic(BaseModel):
|
||||
|
||||
@asynccontextmanager
|
||||
async def graph(config: RunnableConfig):
|
||||
async with MultiServerMCPClient() as mcp_client:
|
||||
cfg = GraphConfigPydantic(**config.get("configurable", {}))
|
||||
tools = []
|
||||
cfg = GraphConfigPydantic(**config.get("configurable", {}))
|
||||
tools = []
|
||||
|
||||
supabase_token = config.get("configurable", {}).get("x-supabase-access-token")
|
||||
if cfg.rag and cfg.rag.rag_url and cfg.rag.collections and supabase_token:
|
||||
for collection in cfg.rag.collections:
|
||||
rag_tool = await create_rag_tool(
|
||||
cfg.rag.rag_url, collection, supabase_token
|
||||
)
|
||||
tools.append(rag_tool)
|
||||
supabase_token = config.get("configurable", {}).get("x-supabase-access-token")
|
||||
if cfg.rag and cfg.rag.rag_url and cfg.rag.collections and supabase_token:
|
||||
for collection in cfg.rag.collections:
|
||||
rag_tool = await create_rag_tool(
|
||||
cfg.rag.rag_url, collection, supabase_token
|
||||
)
|
||||
tools.append(rag_tool)
|
||||
|
||||
if (
|
||||
cfg.mcp_config
|
||||
and cfg.mcp_config.url
|
||||
and cfg.mcp_config.tools
|
||||
and (mcp_tokens := await fetch_tokens(config))
|
||||
if (
|
||||
cfg.mcp_config
|
||||
and cfg.mcp_config.url
|
||||
and cfg.mcp_config.tools
|
||||
and (mcp_tokens := await fetch_tokens(config))
|
||||
):
|
||||
url = cfg.mcp_config.url.rstrip("/") + "/mcp"
|
||||
access_token = mcp_tokens["access_token"]
|
||||
async with streamablehttp_client(
|
||||
url,
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
) as (
|
||||
read_stream,
|
||||
write_stream,
|
||||
_,
|
||||
):
|
||||
await mcp_client.connect_to_server(
|
||||
"mcp_server",
|
||||
transport="streamable_http",
|
||||
url=cfg.mcp_config.url.rstrip("/") + "/mcp",
|
||||
headers={"Authorization": f"Bearer {mcp_tokens['access_token']}"},
|
||||
)
|
||||
accumulated_mcp_tools: list[types.Tool] = []
|
||||
|
||||
tools.extend(
|
||||
[
|
||||
wrap_mcp_authenticate_tool(tool)
|
||||
for tool in mcp_client.get_tools()
|
||||
if tool.name in cfg.mcp_config.tools
|
||||
]
|
||||
)
|
||||
async with ClientSession(read_stream, write_stream) as session:
|
||||
await session.initialize()
|
||||
remaining_tool_names_to_find = set(cfg.mcp_config.tools)
|
||||
cursor: str | None = None
|
||||
|
||||
model = init_chat_model(
|
||||
cfg.model_name,
|
||||
temperature=cfg.temperature,
|
||||
max_tokens=cfg.max_tokens,
|
||||
)
|
||||
while True:
|
||||
mcp_tools = await session.list_tools(cursor=cursor)
|
||||
|
||||
yield create_react_agent(
|
||||
prompt=cfg.system_prompt + UNEDITABLE_SYSTEM_PROMPT,
|
||||
model=model,
|
||||
tools=tools,
|
||||
config_schema=GraphConfigPydantic,
|
||||
)
|
||||
if cursor is not None and mcp_tools.nextCursor == cursor:
|
||||
raise Exception("New cursor is same as old cursor")
|
||||
|
||||
cursor = mcp_tools.nextCursor
|
||||
|
||||
if mcp_tools.tools:
|
||||
for tool in mcp_tools.tools:
|
||||
if tool.name in remaining_tool_names_to_find:
|
||||
accumulated_mcp_tools.append(tool)
|
||||
tools.append(
|
||||
await wrap_mcp_authenticate_tool(
|
||||
tool, access_token=access_token, mcp_url=url
|
||||
)
|
||||
)
|
||||
remaining_tool_names_to_find.remove(tool.name)
|
||||
|
||||
all_required_tools_found = not remaining_tool_names_to_find
|
||||
no_more_pages = not mcp_tools.nextCursor
|
||||
|
||||
if all_required_tools_found or no_more_pages:
|
||||
break
|
||||
|
||||
model = init_chat_model(
|
||||
cfg.model_name,
|
||||
temperature=cfg.temperature,
|
||||
max_tokens=cfg.max_tokens,
|
||||
)
|
||||
|
||||
yield create_react_agent(
|
||||
prompt=cfg.system_prompt + UNEDITABLE_SYSTEM_PROMPT,
|
||||
model=model,
|
||||
tools=tools,
|
||||
config_schema=GraphConfigPydantic,
|
||||
)
|
||||
|
||||
@@ -89,9 +89,6 @@ async def on_thread_create(
|
||||
metadata = value.setdefault("metadata", {})
|
||||
metadata["owner"] = ctx.user.identity
|
||||
|
||||
# Return filter to restrict access to just the creator
|
||||
return {"owner": ctx.user.identity}
|
||||
|
||||
|
||||
@auth.on.threads.read
|
||||
@auth.on.threads.delete
|
||||
@@ -126,9 +123,6 @@ async def on_assistants_create(
|
||||
metadata = value.setdefault("metadata", {})
|
||||
metadata["owner"] = ctx.user.identity
|
||||
|
||||
# Return filter to restrict access to just the creator
|
||||
return {"owner": ctx.user.identity}
|
||||
|
||||
|
||||
@auth.on.assistants.read
|
||||
@auth.on.assistants.delete
|
||||
|
||||
+35
-18
@@ -1,34 +1,51 @@
|
||||
from typing import Annotated
|
||||
from langchain_core.tools import StructuredTool, ToolException, tool
|
||||
from langchain_core.tools import BaseTool, ToolException, tool
|
||||
import aiohttp
|
||||
import re
|
||||
from mcp import McpError
|
||||
from mcp import types
|
||||
from mcp import ClientSession
|
||||
from mcp.client.streamable_http import streamablehttp_client
|
||||
|
||||
|
||||
def wrap_mcp_authenticate_tool(tool: StructuredTool) -> StructuredTool:
|
||||
async def wrap_mcp_authenticate_tool(
|
||||
mcp_tool: types.Tool, access_token: str = "", mcp_url: str = ""
|
||||
) -> BaseTool:
|
||||
"""Wrap the tool coroutine to handle `interaction_required` MCP error.
|
||||
|
||||
Tried to obtain the URL from the error, which the LLM can use to render a link."""
|
||||
|
||||
old_coroutine = tool.coroutine
|
||||
@tool(
|
||||
mcp_tool.name,
|
||||
description=mcp_tool.description,
|
||||
args_schema=mcp_tool.inputSchema,
|
||||
)
|
||||
async def langchain_tool(**kwargs):
|
||||
async with streamablehttp_client(
|
||||
mcp_url,
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
) as (
|
||||
read_stream,
|
||||
write_stream,
|
||||
_,
|
||||
):
|
||||
async with ClientSession(read_stream, write_stream) as session:
|
||||
await session.initialize()
|
||||
try:
|
||||
result = await session.call_tool(mcp_tool.name, kwargs)
|
||||
return result.content
|
||||
except McpError as e:
|
||||
if e.error.code == -32003 and e.error.data:
|
||||
error_message = (
|
||||
((e.error.data or {}).get("message") or {}).get("text")
|
||||
) or "Required interaction"
|
||||
|
||||
async def wrapped_mcp_coroutine(**kwargs):
|
||||
try:
|
||||
return await old_coroutine(**kwargs)
|
||||
except McpError as e:
|
||||
if e.error.code == -32003 and e.error.data:
|
||||
error_message = (
|
||||
((e.error.data or {}).get("message") or {}).get("text")
|
||||
) or "Required interaction"
|
||||
if url := (e.error.data or {}).get("url"):
|
||||
error_message += f": {url}"
|
||||
|
||||
if url := (e.error.data or {}).get("url"):
|
||||
error_message += f": {url}"
|
||||
raise ToolException(error_message)
|
||||
|
||||
raise ToolException(error_message)
|
||||
raise e
|
||||
|
||||
tool.coroutine = wrapped_mcp_coroutine
|
||||
return tool
|
||||
return langchain_tool
|
||||
|
||||
|
||||
async def create_rag_tool(rag_url: str, collection_id: str, access_token: str):
|
||||
|
||||
@@ -622,19 +622,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/30/40/aa440a7cd05f1dab5d7c91a1284eb776c3cf3eb59fa18ed39927650cfa38/langchain_core-0.3.59-py3-none-any.whl", hash = "sha256:9686baaff43f2c8175535da13faf40e6866769015e93130c3c1e4243e7244d70", size = 437656 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "langchain-mcp-adapters"
|
||||
version = "0.0.11"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
{ name = "mcp" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9d/a8/931c13137f6f87b791d77241f0593f4a805b62e7161515d22f8334446d8d/langchain_mcp_adapters-0.0.11.tar.gz", hash = "sha256:664b0aac83f0b41c2a0d00344a608a59df55b07e27cde303a26334c7e3ea4051", size = 15628 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/59/0d/c896c194222965494a7be56513d1da452ece4111e19bb97de1b3677e289b/langchain_mcp_adapters-0.0.11-py3-none-any.whl", hash = "sha256:e10a5ee6b5823fe51d93439011bfc92d9d929ff16b8e982ba5bf51d0bcf650af", size = 10652 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "langchain-openai"
|
||||
version = "0.3.16"
|
||||
@@ -680,7 +667,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-api"
|
||||
version = "0.2.21"
|
||||
version = "0.2.27"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cloudpickle" },
|
||||
@@ -703,9 +690,9 @@ dependencies = [
|
||||
{ name = "uvicorn" },
|
||||
{ name = "watchfiles" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cb/ba/590c7f25a95c84196387546fbbcc0d921cf42dcb84c37751c613b92eeb99/langgraph_api-0.2.21.tar.gz", hash = "sha256:6a9802fd9134b188e0bb024b28d1bf413cb04185e096ec76481660e461e4c82f", size = 170317 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6c/39/796960b1c6d6196f3119081e6072d5a53797003c9695d576550c5590e346/langgraph_api-0.2.27.tar.gz", hash = "sha256:d53c77456de3888164fde8f1703b050c245aebdab3ba42b1868d4bfe319343f5", size = 172523 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/6a/5cdeb95165bf3e318a215f6a2bd347639da37af7b1db7f940ea25723be04/langgraph_api-0.2.21-py3-none-any.whl", hash = "sha256:fa26b9727b4538158a37b1cbf3fe8f42766bbbbf192aa7abaef092dcb477ae92", size = 205528 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/0a/d224b694ae1033b90067096cf19ff0628f55053f267cea2c3224cd1e5417/langgraph_api-0.2.27-py3-none-any.whl", hash = "sha256:f2f6ec669e22f2ab6ebaa971573c9b3bdade8d83a968cfa3493de85b154b418b", size = 208097 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -794,7 +781,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "mcp"
|
||||
version = "1.8.0"
|
||||
version = "1.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
@@ -807,9 +794,9 @@ dependencies = [
|
||||
{ name = "starlette" },
|
||||
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ff/97/0a3e08559557b0ac5799f9fb535fbe5a4e4dcdd66ce9d32e7a74b4d0534d/mcp-1.8.0.tar.gz", hash = "sha256:263dfb700540b726c093f0c3e043f66aded0730d0b51f04eb0a3eb90055fe49b", size = 264641 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bc/8d/0f4468582e9e97b0a24604b585c651dfd2144300ecffd1c06a680f5c8861/mcp-1.9.0.tar.gz", hash = "sha256:905d8d208baf7e3e71d70c82803b89112e321581bcd2530f9de0fe4103d28749", size = 281432 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/b2/4ac3bd17b1fdd65658f18de4eb0c703517ee0b483dc5f56467802a9197e0/mcp-1.8.0-py3-none-any.whl", hash = "sha256:889d9d3b4f12b7da59e7a3933a0acadae1fce498bfcd220defb590aa291a1334", size = 119544 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/d5/22e36c95c83c80eb47c83f231095419cf57cf5cca5416f1c960032074c78/mcp-1.9.0-py3-none-any.whl", hash = "sha256:9dfb89c8c56f742da10a5910a1f64b0d2ac2c3ed2bd572ddb1cfab7f35957178", size = 125082 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1465,9 +1452,9 @@ dependencies = [
|
||||
{ name = "langchain" },
|
||||
{ name = "langchain-anthropic" },
|
||||
{ name = "langchain-core" },
|
||||
{ name = "langchain-mcp-adapters" },
|
||||
{ name = "langchain-openai" },
|
||||
{ name = "langgraph" },
|
||||
{ name = "mcp" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "supabase" },
|
||||
]
|
||||
@@ -1486,16 +1473,16 @@ requires-dist = [
|
||||
{ name = "langchain", specifier = "==0.3.25" },
|
||||
{ name = "langchain-anthropic", specifier = "==0.3.13" },
|
||||
{ name = "langchain-core", specifier = "==0.3.59" },
|
||||
{ name = "langchain-mcp-adapters", specifier = "==0.0.11" },
|
||||
{ name = "langchain-openai", specifier = "==0.3.16" },
|
||||
{ name = "langgraph", specifier = "==0.4.3" },
|
||||
{ name = "mcp", specifier = ">=1.9.0" },
|
||||
{ name = "pydantic", specifier = "==2.11.3" },
|
||||
{ name = "supabase", specifier = ">=2.15.1" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "langgraph-api", specifier = "==0.2.21" },
|
||||
{ name = "langgraph-api", specifier = "==0.2.27" },
|
||||
{ name = "langgraph-cli", specifier = "==0.2.10" },
|
||||
{ name = "langgraph-runtime-inmem", specifier = ">=0.0.11" },
|
||||
{ name = "ruff", specifier = ">=0.8.4" },
|
||||
|
||||
Reference in New Issue
Block a user