mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-06-26 22:19:57 +00:00
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:
parent
0427eebe94
commit
03656da4dd
@ -148,9 +148,9 @@ function start_mcp_server() {
|
||||
"$PY" "${MCP_SCRIPT_PATH}" \
|
||||
--host="${MCP_HOST}" \
|
||||
--port="${MCP_PORT}" \
|
||||
--base_url="${MCP_BASE_URL}" \
|
||||
--base-url="${MCP_BASE_URL}" \
|
||||
--mode="${MCP_MODE}" \
|
||||
--api_key="${MCP_HOST_API_KEY}" &
|
||||
--api-key="${MCP_HOST_API_KEY}" &
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
@ -41,11 +41,11 @@ You can start an MCP server either from source code or via Docker.
|
||||
|
||||
```bash
|
||||
# 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 --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 --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:
|
||||
# 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:
|
||||
@ -194,4 +194,4 @@ The use of an API key depends on the operating mode of your MCP server.
|
||||
- If launching from source, include the API key in the command.
|
||||
- If launching from Docker, update the API key in **docker/docker-compose.yml**.
|
||||
- **Host mode**:
|
||||
If your RAGFlow MCP server is working in host mode, include the API key in the `headers` of your client requests to authenticate your client with the RAGFlow server. An example is available [here](https://github.com/infiniflow/ragflow/blob/main/mcp/client/client.py).
|
||||
If your RAGFlow MCP server is working in host mode, include the API key in the `headers` of your client requests to authenticate your client with the RAGFlow server. An example is available [here](https://github.com/infiniflow/ragflow/blob/main/mcp/client/client.py).
|
||||
|
@ -19,11 +19,11 @@ from collections.abc import AsyncIterator
|
||||
from contextlib import asynccontextmanager
|
||||
from functools import wraps
|
||||
|
||||
import click
|
||||
import requests
|
||||
from starlette.applications import Starlette
|
||||
from starlette.middleware import Middleware
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from starlette.responses import JSONResponse
|
||||
from starlette.responses import JSONResponse, Response
|
||||
from starlette.routing import Mount, Route
|
||||
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 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)}))
|
||||
|
||||
|
||||
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)
|
||||
return Response()
|
||||
|
||||
|
||||
def create_starlette_app():
|
||||
middleware = None
|
||||
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)]
|
||||
|
||||
return Starlette(
|
||||
debug=True,
|
||||
routes=[
|
||||
Route("/sse", endpoint=handle_sse),
|
||||
Route("/sse", endpoint=handle_sse, methods=["GET"]),
|
||||
Mount("/messages/", app=sse.handle_post_message),
|
||||
],
|
||||
middleware=middleware,
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@click.command()
|
||||
@click.option("--base-url", type=str, default="http://127.0.0.1:9380", help="API base URL for RAGFlow backend")
|
||||
@click.option("--host", type=str, default="127.0.0.1", help="Host to bind the RAGFlow MCP server")
|
||||
@click.option("--port", type=int, default=9382, help="Port to bind the RAGFlow MCP server")
|
||||
@click.option(
|
||||
"--mode",
|
||||
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"),
|
||||
)
|
||||
@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 uvicorn
|
||||
@ -260,31 +274,15 @@ if __name__ == "__main__":
|
||||
|
||||
load_dotenv()
|
||||
|
||||
parser = argparse.ArgumentParser(description="RAGFlow MCP Server")
|
||||
parser.add_argument("--base_url", type=str, default="http://127.0.0.1:9380", help="api_url: http://<host_address>")
|
||||
parser.add_argument("--host", type=str, default="127.0.0.1", help="RAGFlow MCP SERVER host")
|
||||
parser.add_argument("--port", type=str, default="9382", help="RAGFlow MCP SERVER port")
|
||||
parser.add_argument(
|
||||
"--mode",
|
||||
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'")
|
||||
global BASE_URL, HOST, PORT, MODE, HOST_API_KEY
|
||||
BASE_URL = os.environ.get("RAGFLOW_MCP_BASE_URL", base_url)
|
||||
HOST = os.environ.get("RAGFLOW_MCP_HOST", host)
|
||||
PORT = os.environ.get("RAGFLOW_MCP_PORT", str(port))
|
||||
MODE = os.environ.get("RAGFLOW_MCP_LAUNCH_MODE", mode)
|
||||
HOST_API_KEY = os.environ.get("RAGFLOW_MCP_HOST_API_KEY", api_key)
|
||||
|
||||
BASE_URL = os.environ.get("RAGFLOW_MCP_BASE_URL", args.base_url)
|
||||
HOST = os.environ.get("RAGFLOW_MCP_HOST", args.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)
|
||||
if MODE == "self-host" and not HOST_API_KEY:
|
||||
raise click.UsageError("--api-key is required when --mode is 'self-host'")
|
||||
|
||||
print(
|
||||
r"""
|
||||
@ -293,7 +291,7 @@ __ __ ____ ____ ____ _____ ______ _______ ____
|
||||
| |\/| | | | |_) | \___ \| _| | |_) \ \ / /| _| | |_) |
|
||||
| | | | |___| __/ ___) | |___| _ < \ V / | |___| _ <
|
||||
|_| |_|\____|_| |____/|_____|_| \_\ \_/ |_____|_| \_\
|
||||
""",
|
||||
""",
|
||||
flush=True,
|
||||
)
|
||||
print(f"MCP launch mode: {MODE}", flush=True)
|
||||
@ -306,3 +304,14 @@ __ __ ____ ____ ____ _____ ______ _______ ____
|
||||
host=HOST,
|
||||
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()
|
||||
|
@ -126,9 +126,10 @@ dependencies = [
|
||||
"trio>=0.29.0",
|
||||
"langfuse>=2.60.0",
|
||||
"debugpy>=1.8.13",
|
||||
"mcp>=1.6.0",
|
||||
"mcp>=1.9.4",
|
||||
"opensearch-py==2.7.1",
|
||||
"pluginlib==0.9.4",
|
||||
"click>=8.1.8",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
22
uv.lock
generated
22
uv.lock
generated
@ -3010,7 +3010,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "mcp"
|
||||
version = "1.6.0"
|
||||
version = "1.9.4"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
@ -3018,13 +3018,14 @@ dependencies = [
|
||||
{ name = "httpx-sse" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "python-multipart" },
|
||||
{ name = "sse-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 = [
|
||||
{ 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]]
|
||||
@ -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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "python-pptx"
|
||||
version = "1.0.2"
|
||||
@ -4868,6 +4878,7 @@ dependencies = [
|
||||
{ name = "botocore" },
|
||||
{ name = "cachetools" },
|
||||
{ name = "chardet" },
|
||||
{ name = "click" },
|
||||
{ name = "cn2an" },
|
||||
{ name = "cohere" },
|
||||
{ name = "crawl4ai" },
|
||||
@ -5016,6 +5027,7 @@ requires-dist = [
|
||||
{ name = "botocore", specifier = "==1.34.140" },
|
||||
{ name = "cachetools", specifier = "==5.3.3" },
|
||||
{ name = "chardet", specifier = "==5.2.0" },
|
||||
{ name = "click", specifier = ">=8.1.8" },
|
||||
{ name = "cn2an", specifier = "==0.5.22" },
|
||||
{ name = "cohere", specifier = "==5.6.2" },
|
||||
{ name = "crawl4ai", specifier = "==0.3.8" },
|
||||
@ -5054,7 +5066,7 @@ requires-dist = [
|
||||
{ name = "langfuse", specifier = ">=2.60.0" },
|
||||
{ name = "markdown", specifier = "==3.6" },
|
||||
{ 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 = "minio", specifier = "==7.2.4" },
|
||||
{ name = "mistralai", specifier = "==0.4.2" },
|
||||
|
Loading…
x
Reference in New Issue
Block a user