Files
3c8578e9f4 feat(mcp): add Streamable HTTP transport (#736)
* feat(mcp): add Streamable HTTP transport

Add a modern MCP 2026-07-28 HTTP endpoint for standalone and Web modes, with shared listener configuration, authentication, schema, tests, and bilingual documentation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix(tests): support Python 2 HTTP assertions

Normalize response header names and call handler methods through their underlying functions so the HTTP tests behave consistently on Python 2.7 and Python 3.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix(mcp): harden HTTP request handling

Validate mirrored request IDs, align Config Studio listener checks, bound socket reads, and accept the binary wrapper's documented SIGINT exit status.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix(mcp): align HTTP negotiation validation

Reject unavailable Accept media types by quality and keep schema host validation consistent with runtime listener checks.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix(tests): validate host schema behavior

Assert accepted and rejected host samples instead of comparing Unicode escape representations that differ on Python 2.7.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix(tests): avoid cross-engine schema regex

Keep the schema pattern contract without executing its ECMA-262 Unicode escape through Python 2's incompatible regex engine.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix(mcp): validate HTTP request boundaries

Reject empty tokens and malformed JSON-RPC request shapes, and keep Config Studio bracket validation aligned with runtime listener handling.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix(mcp): tighten HTTP input validation

Reject legacy notifications, nested host brackets, and document the required JSON content type so validation remains consistent across runtime, schema, and Config Studio.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix(mcp): validate Origin before method errors

Apply the existing Origin policy to non-POST MCP requests and return CORS headers for configured allowed origins.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* docs(mcp): clarify proxy Origin allowlist

Explain that browser clients behind TLS termination must allowlist the public HTTPS origin even when the public page appears same-origin.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* docs(mcp): add standalone server guide

Move MCP client setup, transports, tools, security, and protocol limits into dedicated Chinese and English pages with matching navigation and index links.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix(mcp): reject duplicate routing headers

Reject repeated MCP protocol, method, and name headers before body consistency checks across Python 2 and Python 3.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix(mcp): validate named request headers

Validate Mcp-Name for tools/call, prompts/get, and resources/read before dispatching unsupported methods.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-23 21:36:26 +08:00

80 lines
2.5 KiB
Python

# -*- coding:utf-8 -*-
"""
Configuration loader for DDNS environment variables.
@author: NewFuture
"""
from ast import literal_eval
from os import environ
from sys import stderr
__all__ = ["load_config"]
def _try_parse_array(value, key=None):
# type: (str, str|None) -> Any
"""解析数组值[], 非数组返回元素字符串(去除前后空格)"""
if not value:
return value
value = value.strip()
# 尝试解析 JSON
# if (value.startswith("{'") and value.endswith("'}")) or (value.startswith('{"') and value.endswith('"}')):
# try:
# return json_decode(value)
# except Exception:
# logging.warning("Failed to parse JSON from value: %s", value)
if value.startswith("[") and value.endswith("]"):
# or (value.startswith("{") and value.endswith("}"))
try:
return literal_eval(value)
except Exception:
stderr.write("Failed to parse JSON array from value: {}={}\n".format(key, value))
pass
# 返回去除前后空格的字符串
return value
def load_config(prefix="DDNS_"):
# type: (str) -> dict
"""
从环境变量加载配置并返回配置字典。
支持以下转换:
1. 对于特定的数组参数(index4, index6, ipv4, ipv6, proxy),转换为数组
2. 对于 JSON 格式的数组 [item1,item2],转换为数组
3. 键名转换:点号转下划线,支持大小写变体
4. 自动检测标准 Python 环境变量:
- SSL 验证:PYTHONHTTPSVERIFY
5. 支持 extra 字段:DDNS_EXTRA_XXX 会被转换为 extra_xxx
6. 其他所有值保持原始字符串格式,去除前后空格
Args:
prefix (str): 环境变量前缀,默认为 "DDNS_"
Returns:
dict: 从环境变量解析的配置字典
"""
env_vars = {} # type: dict[str, str | list]
# 标准环境变量映射
alias_mappings = {"pythonhttpsverify": "ssl"}
for key, value in environ.items():
lower_key = key.lower()
config_key = None
if lower_key in alias_mappings:
config_key = alias_mappings[lower_key]
if config_key in env_vars:
continue # DDNS变量优先级更高
elif lower_key.startswith(prefix.lower()):
config_key = lower_key[len(prefix) :].replace(".", "_") # noqa: E203
if config_key:
env_vars[config_key] = (
value.strip() if config_key in ("token", "http_token") else _try_parse_array(value, key=key)
)
return env_vars