From 0740dd1aa8edf853c4935beacd8c49b2830a9477 Mon Sep 17 00:00:00 2001 From: hotwa Date: Mon, 1 Sep 2025 12:00:47 +0800 Subject: [PATCH] =?UTF-8?q?mcp=E6=B5=8B=E8=AF=95=E6=A1=88=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/mcp_adapters/inject_to_langgraph.py | 91 +++++++++++++++++ .../mcp_adapters/server/fastmcp_server.py | 98 +++++++++++++++++++ .../mcp_adapters/server/weather_server.py | 34 +++++++ 3 files changed, 223 insertions(+) create mode 100644 examples/mcp_adapters/inject_to_langgraph.py create mode 100644 examples/mcp_adapters/server/fastmcp_server.py create mode 100644 examples/mcp_adapters/server/weather_server.py diff --git a/examples/mcp_adapters/inject_to_langgraph.py b/examples/mcp_adapters/inject_to_langgraph.py new file mode 100644 index 0000000..0759b2f --- /dev/null +++ b/examples/mcp_adapters/inject_to_langgraph.py @@ -0,0 +1,91 @@ +""" +Full example: Start a FastMCP-style HTTP MCP server, then use +langchain-mcp-adapters to inject MCP tools into a LangGraph + Qwen agent. + +Steps: +1) Start the local HTTP MCP server (fallback minimal): + - uv pip install fastapi uvicorn + - uvicorn examples.mcp_adapters.fastmcp_server:http_app --host 127.0.0.1 --port 8010 + + The MCP endpoint is available at: http://127.0.0.1:8010/mcp/ + +2) Install mcp-adapters: + - uv pip install -e '.[mcp-adapters]' + +3) Configure adapter entry + config (choose one): + A) Explicit entry (recommended): + export MCP_ADAPTER_ENTRY='langchain_mcp_adapters:create_tools' + export MCP_CONFIG_JSON='{"servers":{"local":{"url":"http://127.0.0.1:8010/mcp/","transport":"streamable_http"}}}' + + B) If your adapter provides a different function: + export MCP_ADAPTER_ENTRY='your_module:your_entry' + export MCP_CONFIG_JSON='{}' + +4) Qwen env (or .env auto-loaded): + - QWEN_API_KEY, QWEN_BASE_URL, QWEN_MODEL, ... + +5) Run this example: + - python examples/mcp_adapters/inject_to_langgraph.py +""" + +import json +import os +import importlib +from typing import Any, Dict, List + +import asyncio +from langchain_core.messages import HumanMessage +from langgraph_qwen.chat_model import ChatQwenOpenAICompat +from langgraph.prebuilt import create_react_agent + +def _env(name: str, default: str = "") -> str: + v = os.getenv(name) + return v if v else default + +async def _load_tools_via_client() -> List[Any]: + try: + from langchain_mcp_adapters.client import MultiServerMCPClient # type: ignore + except Exception as e: + raise RuntimeError("Please install langchain-mcp-adapters: uv pip install -e '.[mcp-adapters]'") from e + + weather_url = _env("WEATHER_MCP_URL", "http://localhost:8000/mcp") + weather_transport = _env("WEATHER_TRANSPORT", "streamable_http") + + client = MultiServerMCPClient( + { + "weather": { + "url": weather_url, + "transport": weather_transport, + } + } + ) + tools = await client.get_tools() + # Best-effort cleanup if client exposes a close method + try: + if hasattr(client, "close") and callable(getattr(client, "close")): + await client.close() # type: ignore + elif hasattr(client, "close_all_sessions") and callable(getattr(client, "close_all_sessions")): + await client.close_all_sessions() # type: ignore + except Exception: + pass + return tools + +async def main(): + tools = await _load_tools_via_client() + print("Discovered tools:") + for t in tools: + print(" -", getattr(t, "name", "")) + + model = ChatQwenOpenAICompat(temperature=0).bind(tool_choice="auto") + # 或直接:model = ChatQwenOpenAICompat(temperature=0).bind_tools(tools).bind(tool_choice="auto") + agent = create_react_agent(model, tools) + + prompt = ( + "请先列出可用工具名,然后选择一个合理的工具做一次演示调用,并用简洁中文总结结果。" + ) + res = await agent.ainvoke({"messages": [HumanMessage(content=prompt)]}) + print("=== Final ===") + print(res["messages"][-1].content) + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/mcp_adapters/server/fastmcp_server.py b/examples/mcp_adapters/server/fastmcp_server.py new file mode 100644 index 0000000..7ce92d0 --- /dev/null +++ b/examples/mcp_adapters/server/fastmcp_server.py @@ -0,0 +1,98 @@ +""" +FastMCP-style minimal MCP server for local testing (HTTP JSON-RPC 2.0). + +This script prefers running with FastMCP if installed; otherwise it falls back +to a tiny FastAPI JSON-RPC server that implements initialize/tools.list/tools.call. + +Usage (fallback HTTP server): + uv pip install fastapi uvicorn + uvicorn examples.mcp_adapters.fastmcp_server:http_app --host 127.0.0.1 --port 8010 + +The MCP endpoint will be: http://127.0.0.1:8010/mcp/ + +If you have FastMCP server utilities available, you can replace the fallback +with your real FastMCP server. +""" + +try: + # If you have a real FastMCP server implementation, import and expose here. + # Example (pseudo): + # from fastmcp import MCPServer, tool + # ... define @tool functions ... + # fastmcp_app = MCPServer(...) + # Then you can provide an ASGI http_app = fastmcp_app.asgi_app() + fastmcp_app = None # placeholder; fallback below +except Exception: # pragma: no cover + fastmcp_app = None + +# Fallback: FastAPI JSON-RPC 2.0 minimal server +from fastapi import FastAPI, Request +from fastapi.responses import JSONResponse + +http_app = FastAPI() + + +def _rpc_result(result, id_): + return {"jsonrpc": "2.0", "result": result, "id": id_} + + +def _rpc_error(message, id_=None, code=-32000): + return {"jsonrpc": "2.0", "error": {"code": code, "message": message}, "id": id_} + + +@http_app.post("/mcp/") +async def mcp_endpoint(request: Request): + try: + data = await request.json() + except Exception: + return JSONResponse(_rpc_error("Invalid JSON"), status_code=400) + + method = data.get("method") + id_ = data.get("id") + params = data.get("params") or {} + + if method == "initialize": + return JSONResponse(_rpc_result({"sessionId": "demo-session"}, id_)) + + if method in ("tools/list", "tool/list", "tools.list"): + tools = [ + { + "name": "echo", + "description": "Echo back provided text.", + "input_schema": { + "type": "object", + "properties": {"text": {"type": "string"}}, + "required": ["text"], + }, + }, + { + "name": "add", + "description": "Add two integers a and b.", + "input_schema": { + "type": "object", + "properties": {"a": {"type": "integer"}, "b": {"type": "integer"}}, + "required": ["a", "b"], + }, + }, + ] + return JSONResponse(_rpc_result({"tools": tools}, id_)) + + if method in ("tools/call", "tool/call", "tools.call"): + name = params.get("name") or params.get("tool") + arguments = params.get("arguments") or params.get("params") or {} + if name == "echo": + text = arguments.get("text", "") + result = {"content": [{"type": "text", "text": text}]} + return JSONResponse(_rpc_result(result, id_)) + if name == "add": + try: + a = int(arguments.get("a", 0)) + b = int(arguments.get("b", 0)) + result = {"content": [{"type": "text", "text": str(a + b)}]} + return JSONResponse(_rpc_result(result, id_)) + except Exception: + return JSONResponse(_rpc_error("Invalid arguments for add", id_=id_), status_code=400) + return JSONResponse(_rpc_error("Unknown tool", id_=id_), status_code=400) + + return JSONResponse(_rpc_error("Method not found", id_=id_), status_code=400) + diff --git a/examples/mcp_adapters/server/weather_server.py b/examples/mcp_adapters/server/weather_server.py new file mode 100644 index 0000000..e08c188 --- /dev/null +++ b/examples/mcp_adapters/server/weather_server.py @@ -0,0 +1,34 @@ +from fastapi import FastAPI +from mcp.server.fastmcp import FastMCP +import asyncio +from contextlib import asynccontextmanager + +# 创建 FastAPI 应用 +app = FastAPI() + +# 创建 FastMCP 服务器 +mcp = FastMCP("Weather") + +# 定义工具:获取天气 +@mcp.tool() +async def get_weather(location: str) -> str: + """返回指定位置的天气情况""" + return f"The weather in {location} is sunny!" + +# 使用 FastAPI 的 lifespan 来启动 MCP 服务器 +@asynccontextmanager +async def lifespan(app: FastAPI): + # 在应用启动时启动 MCP 服务 + loop = asyncio.get_event_loop() + task = loop.create_task(mcp.run_streamable_http_async()) + yield + # 在应用关闭时停止 MCP 服务 + task.cancel() + +# 将 lifespan 事件添加到 FastAPI 应用 +app = FastAPI(lifespan=lifespan) + +# 启动 FastAPI 应用 +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000)