Refa: upgrade MCP SDK to v1.9.4 (#8421)

### What problem does this PR solve?

Upgrade MCP SDK to v1.9.4 (latest).

### Type of change

- [x] Refactoring
This commit is contained in:
Yongteng Lei 2025-06-23 16:53:59 +08:00 committed by GitHub
parent 0427eebe94
commit 03656da4dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 89 additions and 67 deletions

View File

@ -148,9 +148,9 @@ function start_mcp_server() {
"$PY" "${MCP_SCRIPT_PATH}" \ "$PY" "${MCP_SCRIPT_PATH}" \
--host="${MCP_HOST}" \ --host="${MCP_HOST}" \
--port="${MCP_PORT}" \ --port="${MCP_PORT}" \
--base_url="${MCP_BASE_URL}" \ --base-url="${MCP_BASE_URL}" \
--mode="${MCP_MODE}" \ --mode="${MCP_MODE}" \
--api_key="${MCP_HOST_API_KEY}" & --api-key="${MCP_HOST_API_KEY}" &
} }
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------

View File

@ -41,11 +41,11 @@ You can start an MCP server either from source code or via Docker.
```bash ```bash
# Launch the MCP server to work in self-host mode, run either of the following # Launch the MCP server to work in self-host mode, run either of the following
uv run mcp/server/server.py --host=127.0.0.1 --port=9382 --base_url=http://127.0.0.1:9380 --api_key=ragflow-xxxxx uv run mcp/server/server.py --host=127.0.0.1 --port=9382 --base-url=http://127.0.0.1:9380 --api-key=ragflow-xxxxx
# uv run mcp/server/server.py --host=127.0.0.1 --port=9382 --base_url=http://127.0.0.1:9380 --mode=self-host --api_key=ragflow-xxxxx # uv run mcp/server/server.py --host=127.0.0.1 --port=9382 --base-url=http://127.0.0.1:9380 --mode=self-host --api-key=ragflow-xxxxx
# To launch the MCP server to work in host mode, run the following instead: # To launch the MCP server to work in host mode, run the following instead:
# uv run mcp/server/server.py --host=127.0.0.1 --port=9382 --base_url=http://127.0.0.1:9380 --mode=host # uv run mcp/server/server.py --host=127.0.0.1 --port=9382 --base-url=http://127.0.0.1:9380 --mode=host
``` ```
Where: Where:

View File

@ -19,11 +19,11 @@ from collections.abc import AsyncIterator
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from functools import wraps from functools import wraps
import click
import requests import requests
from starlette.applications import Starlette from starlette.applications import Starlette
from starlette.middleware import Middleware from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware from starlette.responses import JSONResponse, Response
from starlette.responses import JSONResponse
from starlette.routing import Mount, Route from starlette.routing import Mount, Route
from strenum import StrEnum from strenum import StrEnum
@ -209,50 +209,64 @@ async def call_tool(name: str, arguments: dict, *, connector) -> list[types.Text
async def handle_sse(request): async def handle_sse(request):
async with sse.connect_sse(request.scope, request.receive, request._send) as streams: async with sse.connect_sse(request.scope, request.receive, request._send) as streams:
await app.run(streams[0], streams[1], app.create_initialization_options(experimental_capabilities={"headers": dict(request.headers)})) await app.run(streams[0], streams[1], app.create_initialization_options(experimental_capabilities={"headers": dict(request.headers)}))
return Response()
class AuthMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
# Authentication is deferred, will be handled by RAGFlow core service.
if request.url.path.startswith("/sse") or request.url.path.startswith("/messages"):
token = None
auth_header = request.headers.get("Authorization")
if auth_header and auth_header.startswith("Bearer "):
token = auth_header.removeprefix("Bearer ").strip()
elif request.headers.get("api_key"):
token = request.headers["api_key"]
if not token:
return JSONResponse({"error": "Missing or invalid authorization header"}, status_code=401)
return await call_next(request)
def create_starlette_app(): def create_starlette_app():
middleware = None middleware = None
if MODE == LaunchMode.HOST: if MODE == LaunchMode.HOST:
from starlette.types import ASGIApp, Receive, Scope, Send
class AuthMiddleware:
def __init__(self, app: ASGIApp):
self.app = app
async def __call__(self, scope: Scope, receive: Receive, send: Send):
if scope["type"] != "http":
await self.app(scope, receive, send)
return
path = scope["path"]
if path.startswith("/messages/") or path.startswith("/sse"):
headers = dict(scope["headers"])
token = None
auth_header = headers.get(b"authorization")
if auth_header and auth_header.startswith(b"Bearer "):
token = auth_header.removeprefix(b"Bearer ").strip()
elif b"api_key" in headers:
token = headers[b"api_key"]
if not token:
response = JSONResponse({"error": "Missing or invalid authorization header"}, status_code=401)
await response(scope, receive, send)
return
await self.app(scope, receive, send)
middleware = [Middleware(AuthMiddleware)] middleware = [Middleware(AuthMiddleware)]
return Starlette( return Starlette(
debug=True, debug=True,
routes=[ routes=[
Route("/sse", endpoint=handle_sse), Route("/sse", endpoint=handle_sse, methods=["GET"]),
Mount("/messages/", app=sse.handle_post_message), Mount("/messages/", app=sse.handle_post_message),
], ],
middleware=middleware, middleware=middleware,
) )
if __name__ == "__main__": @click.command()
""" @click.option("--base-url", type=str, default="http://127.0.0.1:9380", help="API base URL for RAGFlow backend")
Launch example: @click.option("--host", type=str, default="127.0.0.1", help="Host to bind the RAGFlow MCP server")
self-host: @click.option("--port", type=int, default=9382, help="Port to bind the RAGFlow MCP server")
uv run mcp/server/server.py --host=127.0.0.1 --port=9382 --base_url=http://127.0.0.1:9380 --mode=self-host --api_key=ragflow-xxxxx @click.option(
host: "--mode",
uv run mcp/server/server.py --host=127.0.0.1 --port=9382 --base_url=http://127.0.0.1:9380 --mode=host type=click.Choice(["self-host", "host"]),
""" default="self-host",
help=("Launch mode:\n self-host: run MCP for a single tenant (requires --api-key)\n host: multi-tenant mode, users must provide Authorization headers"),
import argparse )
@click.option("--api-key", type=str, default="", help="API key to use when in self-host mode")
def main(base_url, host, port, mode, api_key):
import os import os
import uvicorn import uvicorn
@ -260,31 +274,15 @@ if __name__ == "__main__":
load_dotenv() load_dotenv()
parser = argparse.ArgumentParser(description="RAGFlow MCP Server") global BASE_URL, HOST, PORT, MODE, HOST_API_KEY
parser.add_argument("--base_url", type=str, default="http://127.0.0.1:9380", help="api_url: http://<host_address>") BASE_URL = os.environ.get("RAGFLOW_MCP_BASE_URL", base_url)
parser.add_argument("--host", type=str, default="127.0.0.1", help="RAGFlow MCP SERVER host") HOST = os.environ.get("RAGFLOW_MCP_HOST", host)
parser.add_argument("--port", type=str, default="9382", help="RAGFlow MCP SERVER port") PORT = os.environ.get("RAGFLOW_MCP_PORT", str(port))
parser.add_argument( MODE = os.environ.get("RAGFLOW_MCP_LAUNCH_MODE", mode)
"--mode", HOST_API_KEY = os.environ.get("RAGFLOW_MCP_HOST_API_KEY", api_key)
type=str,
default="self-host",
help="Launch mode options:\n"
" * self-host: Launches an MCP server to access a specific tenant space. The 'api_key' argument is required.\n"
" * host: Launches an MCP server that allows users to access their own spaces. Each request must include a Authorization header "
"indicating the user's identification.",
)
parser.add_argument("--api_key", type=str, default="", help="RAGFlow MCP SERVER HOST API KEY")
args = parser.parse_args()
if args.mode not in ["self-host", "host"]:
parser.error("--mode is only accept 'self-host' or 'host'")
if args.mode == "self-host" and not args.api_key:
parser.error("--api_key is required when --mode is 'self-host'")
BASE_URL = os.environ.get("RAGFLOW_MCP_BASE_URL", args.base_url) if MODE == "self-host" and not HOST_API_KEY:
HOST = os.environ.get("RAGFLOW_MCP_HOST", args.host) raise click.UsageError("--api-key is required when --mode is 'self-host'")
PORT = os.environ.get("RAGFLOW_MCP_PORT", args.port)
MODE = os.environ.get("RAGFLOW_MCP_LAUNCH_MODE", args.mode)
HOST_API_KEY = os.environ.get("RAGFLOW_MCP_HOST_API_KEY", args.api_key)
print( print(
r""" r"""
@ -306,3 +304,14 @@ __ __ ____ ____ ____ _____ ______ _______ ____
host=HOST, host=HOST,
port=int(PORT), port=int(PORT),
) )
if __name__ == "__main__":
"""
Launch example:
self-host:
uv run mcp/server/server.py --host=127.0.0.1 --port=9382 --base-url=http://127.0.0.1:9380 --mode=self-host --api-key=ragflow-xxxxx
host:
uv run mcp/server/server.py --host=127.0.0.1 --port=9382 --base-url=http://127.0.0.1:9380 --mode=host
"""
main()

View File

@ -126,9 +126,10 @@ dependencies = [
"trio>=0.29.0", "trio>=0.29.0",
"langfuse>=2.60.0", "langfuse>=2.60.0",
"debugpy>=1.8.13", "debugpy>=1.8.13",
"mcp>=1.6.0", "mcp>=1.9.4",
"opensearch-py==2.7.1", "opensearch-py==2.7.1",
"pluginlib==0.9.4", "pluginlib==0.9.4",
"click>=8.1.8",
] ]
[project.optional-dependencies] [project.optional-dependencies]

22
uv.lock generated
View File

@ -3010,7 +3010,7 @@ wheels = [
[[package]] [[package]]
name = "mcp" name = "mcp"
version = "1.6.0" version = "1.9.4"
source = { registry = "https://mirrors.aliyun.com/pypi/simple" } source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
dependencies = [ dependencies = [
{ name = "anyio" }, { name = "anyio" },
@ -3018,13 +3018,14 @@ dependencies = [
{ name = "httpx-sse" }, { name = "httpx-sse" },
{ name = "pydantic" }, { name = "pydantic" },
{ name = "pydantic-settings" }, { name = "pydantic-settings" },
{ name = "python-multipart" },
{ name = "sse-starlette" }, { name = "sse-starlette" },
{ name = "starlette" }, { name = "starlette" },
{ name = "uvicorn" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" },
] ]
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/95/d2/f587cb965a56e992634bebc8611c5b579af912b74e04eb9164bd49527d21/mcp-1.6.0.tar.gz", hash = "sha256:d9324876de2c5637369f43161cd71eebfd803df5a95e46225cab8d280e366723" } sdist = { url = "https://mirrors.aliyun.com/pypi/packages/06/f2/dc2450e566eeccf92d89a00c3e813234ad58e2ba1e31d11467a09ac4f3b9/mcp-1.9.4.tar.gz", hash = "sha256:cfb0bcd1a9535b42edaef89947b9e18a8feb49362e1cc059d6e7fc636f2cb09f" }
wheels = [ wheels = [
{ url = "https://mirrors.aliyun.com/pypi/packages/10/30/20a7f33b0b884a9d14dd3aa94ff1ac9da1479fe2ad66dd9e2736075d2506/mcp-1.6.0-py3-none-any.whl", hash = "sha256:7bd24c6ea042dbec44c754f100984d186620d8b841ec30f1b19eda9b93a634d0" }, { url = "https://mirrors.aliyun.com/pypi/packages/97/fc/80e655c955137393c443842ffcc4feccab5b12fa7cb8de9ced90f90e6998/mcp-1.9.4-py3-none-any.whl", hash = "sha256:7fcf36b62936adb8e63f89346bccca1268eeca9bf6dfb562ee10b1dfbda9dac0" },
] ]
[[package]] [[package]]
@ -4748,6 +4749,15 @@ wheels = [
{ url = "https://mirrors.aliyun.com/pypi/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a" }, { url = "https://mirrors.aliyun.com/pypi/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a" },
] ]
[[package]]
name = "python-multipart"
version = "0.0.20"
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13" }
wheels = [
{ url = "https://mirrors.aliyun.com/pypi/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104" },
]
[[package]] [[package]]
name = "python-pptx" name = "python-pptx"
version = "1.0.2" version = "1.0.2"
@ -4868,6 +4878,7 @@ dependencies = [
{ name = "botocore" }, { name = "botocore" },
{ name = "cachetools" }, { name = "cachetools" },
{ name = "chardet" }, { name = "chardet" },
{ name = "click" },
{ name = "cn2an" }, { name = "cn2an" },
{ name = "cohere" }, { name = "cohere" },
{ name = "crawl4ai" }, { name = "crawl4ai" },
@ -5016,6 +5027,7 @@ requires-dist = [
{ name = "botocore", specifier = "==1.34.140" }, { name = "botocore", specifier = "==1.34.140" },
{ name = "cachetools", specifier = "==5.3.3" }, { name = "cachetools", specifier = "==5.3.3" },
{ name = "chardet", specifier = "==5.2.0" }, { name = "chardet", specifier = "==5.2.0" },
{ name = "click", specifier = ">=8.1.8" },
{ name = "cn2an", specifier = "==0.5.22" }, { name = "cn2an", specifier = "==0.5.22" },
{ name = "cohere", specifier = "==5.6.2" }, { name = "cohere", specifier = "==5.6.2" },
{ name = "crawl4ai", specifier = "==0.3.8" }, { name = "crawl4ai", specifier = "==0.3.8" },
@ -5054,7 +5066,7 @@ requires-dist = [
{ name = "langfuse", specifier = ">=2.60.0" }, { name = "langfuse", specifier = ">=2.60.0" },
{ name = "markdown", specifier = "==3.6" }, { name = "markdown", specifier = "==3.6" },
{ name = "markdown-to-json", specifier = "==2.1.1" }, { name = "markdown-to-json", specifier = "==2.1.1" },
{ name = "mcp", specifier = ">=1.6.0" }, { name = "mcp", specifier = ">=1.9.4" },
{ name = "mini-racer", specifier = ">=0.12.4,<0.13.0" }, { name = "mini-racer", specifier = ">=0.12.4,<0.13.0" },
{ name = "minio", specifier = "==7.2.4" }, { name = "minio", specifier = "==7.2.4" },
{ name = "mistralai", specifier = "==0.4.2" }, { name = "mistralai", specifier = "==0.4.2" },