mirror of
https://github.com/hansjone/netx.git
synced 2026-10-18 09:00:50 +08:00
MCP: serve tools from the API process over Streamable HTTP at /mcp
- netx_api/mcp_remote: ops / topology / bizMonitor tools, Bearer auth, per-tool RBAC filtering, ?groups= filter, readOnlyHint and group meta. - Mount the official MCP Streamable HTTP app at /mcp (stateless); AuthAuditMiddleware is pure ASGI and skips /mcp. - Let uvicorn chunk MCP responses; some Windows LSPs rewrite Content-Length replies on */mcp* paths into broken chunked framing. - NETX_MCP_ALLOWED_HOSTS opts into DNS-rebinding checks (off by default so LAN and reverse-proxy hosts work; Bearer is the gate). - Remove the three stdio Python MCP packages; tests moved to /mcp, including real-TCP framing regressions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
918ece86c3
commit
52679a7cfc
150 changed files with 2367 additions and 2812 deletions
52
netx_api/mcp_remote/context.py
Normal file
52
netx_api/mcp_remote/context.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
"""Per-request context for built-in remote MCP (token, scopes, groups, lang)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from contextvars import ContextVar
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class McpRequestContext:
|
||||
token: str = ""
|
||||
scopes: frozenset[str] | None = None
|
||||
groups: frozenset[str] = field(default_factory=frozenset)
|
||||
lang: str = "zh"
|
||||
user_id: str = ""
|
||||
auth_via: str = ""
|
||||
|
||||
|
||||
_CTX: ContextVar[McpRequestContext | None] = ContextVar("netx_mcp_request_ctx", default=None)
|
||||
_ASGI_APP: ContextVar[Any] = ContextVar("netx_mcp_asgi_app", default=None)
|
||||
|
||||
# Process-wide FastAPI app reference for in-process ASGI calls (set at mount).
|
||||
_APP_REF: Any = None
|
||||
|
||||
|
||||
def set_app(app: Any) -> None:
|
||||
global _APP_REF
|
||||
_APP_REF = app
|
||||
|
||||
|
||||
def get_app() -> Any:
|
||||
return _ASGI_APP.get() or _APP_REF
|
||||
|
||||
|
||||
def set_request_context(ctx: McpRequestContext | None) -> Any:
|
||||
return _CTX.set(ctx)
|
||||
|
||||
|
||||
def reset_request_context(token: Any) -> None:
|
||||
_CTX.reset(token)
|
||||
|
||||
|
||||
def get_request_context() -> McpRequestContext | None:
|
||||
return _CTX.get()
|
||||
|
||||
|
||||
def require_request_context() -> McpRequestContext:
|
||||
ctx = _CTX.get()
|
||||
if ctx is None or not ctx.token:
|
||||
raise RuntimeError("mcp_request_context_missing")
|
||||
return ctx
|
||||
Loading…
Add table
Add a link
Reference in a new issue