mirror of
https://github.com/open-webui/mcpo
synced 2025-06-26 18:26:58 +00:00
feat: headers support
This commit is contained in:
parent
a33ab53331
commit
dc4c4abe08
12
README.md
12
README.md
@ -46,6 +46,12 @@ To use an SSE-compatible MCP server, simply specify the server type and endpoint
|
|||||||
mcpo --port 8000 --api-key "top-secret" --server-type "sse" -- http://127.0.0.1:8001/sse
|
mcpo --port 8000 --api-key "top-secret" --server-type "sse" -- http://127.0.0.1:8001/sse
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can also provide headers for the SSE connection:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mcpo --port 8000 --api-key "top-secret" --server-type "sse" --headers '{"Authorization": "Bearer token", "X-Custom-Header": "value"}' -- http://127.0.0.1:8001/sse
|
||||||
|
```
|
||||||
|
|
||||||
To use a Streamable HTTP-compatible MCP server, specify the server type and endpoint:
|
To use a Streamable HTTP-compatible MCP server, specify the server type and endpoint:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -93,7 +99,11 @@ Example config.json:
|
|||||||
},
|
},
|
||||||
"mcp_sse": {
|
"mcp_sse": {
|
||||||
"type": "sse", // Explicitly define type
|
"type": "sse", // Explicitly define type
|
||||||
"url": "http://127.0.0.1:8001/sse"
|
"url": "http://127.0.0.1:8001/sse",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer token",
|
||||||
|
"X-Custom-Header": "value"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"mcp_streamable_http": {
|
"mcp_streamable_http": {
|
||||||
"type": "streamable_http",
|
"type": "streamable_http",
|
||||||
|
|||||||
@ -28,7 +28,9 @@ def main(
|
|||||||
] = None,
|
] = None,
|
||||||
strict_auth: Annotated[
|
strict_auth: Annotated[
|
||||||
Optional[bool],
|
Optional[bool],
|
||||||
typer.Option("--strict-auth", help="API key protects all endpoints and documentation"),
|
typer.Option(
|
||||||
|
"--strict-auth", help="API key protects all endpoints and documentation"
|
||||||
|
),
|
||||||
] = False,
|
] = False,
|
||||||
env: Annotated[
|
env: Annotated[
|
||||||
Optional[List[str]], typer.Option("--env", "-e", help="Environment variables")
|
Optional[List[str]], typer.Option("--env", "-e", help="Environment variables")
|
||||||
@ -61,6 +63,9 @@ def main(
|
|||||||
path_prefix: Annotated[
|
path_prefix: Annotated[
|
||||||
Optional[str], typer.Option("--path-prefix", help="URL prefix")
|
Optional[str], typer.Option("--path-prefix", help="URL prefix")
|
||||||
] = None,
|
] = None,
|
||||||
|
headers: Annotated[
|
||||||
|
Optional[str], typer.Option("--header", "-H", help="Headers in JSON format")
|
||||||
|
] = None,
|
||||||
):
|
):
|
||||||
server_command = None
|
server_command = None
|
||||||
if not config_path:
|
if not config_path:
|
||||||
@ -131,6 +136,7 @@ def main(
|
|||||||
ssl_certfile=ssl_certfile,
|
ssl_certfile=ssl_certfile,
|
||||||
ssl_keyfile=ssl_keyfile,
|
ssl_keyfile=ssl_keyfile,
|
||||||
path_prefix=path_prefix,
|
path_prefix=path_prefix,
|
||||||
|
headers=headers,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -116,7 +116,10 @@ async def lifespan(app: FastAPI):
|
|||||||
await create_dynamic_endpoints(app, api_dependency=api_dependency)
|
await create_dynamic_endpoints(app, api_dependency=api_dependency)
|
||||||
yield
|
yield
|
||||||
if server_type == "sse":
|
if server_type == "sse":
|
||||||
async with sse_client(url=args[0], sse_read_timeout=None) as (
|
headers = getattr(app.state, "headers", None)
|
||||||
|
async with sse_client(
|
||||||
|
url=args[0], sse_read_timeout=None, headers=headers
|
||||||
|
) as (
|
||||||
reader,
|
reader,
|
||||||
writer,
|
writer,
|
||||||
):
|
):
|
||||||
@ -125,13 +128,15 @@ async def lifespan(app: FastAPI):
|
|||||||
await create_dynamic_endpoints(app, api_dependency=api_dependency)
|
await create_dynamic_endpoints(app, api_dependency=api_dependency)
|
||||||
yield
|
yield
|
||||||
if server_type == "streamablehttp" or server_type == "streamable_http":
|
if server_type == "streamablehttp" or server_type == "streamable_http":
|
||||||
|
headers = getattr(app.state, "headers", None)
|
||||||
|
|
||||||
# Ensure URL has trailing slash to avoid redirects
|
# Ensure URL has trailing slash to avoid redirects
|
||||||
url = args[0]
|
url = args[0]
|
||||||
if not url.endswith("/"):
|
if not url.endswith("/"):
|
||||||
url = f"{url}/"
|
url = f"{url}/"
|
||||||
|
|
||||||
# Connect using streamablehttp_client from the SDK, similar to sse_client
|
# Connect using streamablehttp_client from the SDK, similar to sse_client
|
||||||
async with streamablehttp_client(url=url) as (
|
async with streamablehttp_client(url=url, headers=headers) as (
|
||||||
reader,
|
reader,
|
||||||
writer,
|
writer,
|
||||||
_, # get_session_id callback not needed for ClientSession
|
_, # get_session_id callback not needed for ClientSession
|
||||||
@ -212,6 +217,14 @@ async def run(
|
|||||||
if api_key and strict_auth:
|
if api_key and strict_auth:
|
||||||
main_app.add_middleware(APIKeyMiddleware, api_key=api_key)
|
main_app.add_middleware(APIKeyMiddleware, api_key=api_key)
|
||||||
|
|
||||||
|
headers = kwargs.get("headers")
|
||||||
|
if headers and isinstance(headers, str):
|
||||||
|
try:
|
||||||
|
headers = json.loads(headers)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print("Warning: Invalid JSON format for headers. Headers will be ignored.")
|
||||||
|
headers = None
|
||||||
|
|
||||||
if server_type == "sse":
|
if server_type == "sse":
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Configuring for a single SSE MCP Server with URL {server_command[0]}"
|
f"Configuring for a single SSE MCP Server with URL {server_command[0]}"
|
||||||
@ -219,6 +232,7 @@ async def run(
|
|||||||
main_app.state.server_type = "sse"
|
main_app.state.server_type = "sse"
|
||||||
main_app.state.args = server_command[0] # Expects URL as the first element
|
main_app.state.args = server_command[0] # Expects URL as the first element
|
||||||
main_app.state.api_dependency = api_dependency
|
main_app.state.api_dependency = api_dependency
|
||||||
|
main_app.state.headers = headers
|
||||||
elif server_type == "streamablehttp" or server_type == "streamable_http":
|
elif server_type == "streamablehttp" or server_type == "streamable_http":
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Configuring for a single StreamableHTTP MCP Server with URL {server_command[0]}"
|
f"Configuring for a single StreamableHTTP MCP Server with URL {server_command[0]}"
|
||||||
@ -226,6 +240,7 @@ async def run(
|
|||||||
main_app.state.server_type = "streamablehttp"
|
main_app.state.server_type = "streamablehttp"
|
||||||
main_app.state.args = server_command[0] # Expects URL as the first element
|
main_app.state.args = server_command[0] # Expects URL as the first element
|
||||||
main_app.state.api_dependency = api_dependency
|
main_app.state.api_dependency = api_dependency
|
||||||
|
main_app.state.headers = headers
|
||||||
elif server_command: # This handles stdio
|
elif server_command: # This handles stdio
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Configuring for a single Stdio MCP Server with command: {' '.join(server_command)}"
|
f"Configuring for a single Stdio MCP Server with command: {' '.join(server_command)}"
|
||||||
@ -306,6 +321,7 @@ async def run(
|
|||||||
if server_config_type == "sse" and server_cfg.get("url"):
|
if server_config_type == "sse" and server_cfg.get("url"):
|
||||||
sub_app.state.server_type = "sse"
|
sub_app.state.server_type = "sse"
|
||||||
sub_app.state.args = server_cfg["url"]
|
sub_app.state.args = server_cfg["url"]
|
||||||
|
sub_app.state.headers = server_cfg.get("headers")
|
||||||
elif (
|
elif (
|
||||||
server_config_type == "streamablehttp"
|
server_config_type == "streamablehttp"
|
||||||
or server_config_type == "streamable_http"
|
or server_config_type == "streamable_http"
|
||||||
@ -316,11 +332,14 @@ async def run(
|
|||||||
url = f"{url}/"
|
url = f"{url}/"
|
||||||
sub_app.state.server_type = "streamablehttp"
|
sub_app.state.server_type = "streamablehttp"
|
||||||
sub_app.state.args = url
|
sub_app.state.args = url
|
||||||
|
sub_app.state.headers = server_cfg.get("headers")
|
||||||
|
|
||||||
elif not server_config_type and server_cfg.get(
|
elif not server_config_type and server_cfg.get(
|
||||||
"url"
|
"url"
|
||||||
): # Fallback for old SSE config
|
): # Fallback for old SSE config
|
||||||
sub_app.state.server_type = "sse"
|
sub_app.state.server_type = "sse"
|
||||||
sub_app.state.args = server_cfg["url"]
|
sub_app.state.args = server_cfg["url"]
|
||||||
|
sub_app.state.headers = server_cfg.get("headers")
|
||||||
|
|
||||||
# Add middleware to protect also documentation and spec
|
# Add middleware to protect also documentation and spec
|
||||||
if api_key and strict_auth:
|
if api_key and strict_auth:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user