81 lines
2.7 KiB
Python
81 lines
2.7 KiB
Python
"""Minimal JSON-lines stdio bridge; tokens are read only for the call and never emitted."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from collections.abc import Callable, Iterable, Iterator, Mapping
|
|
from typing import Any, Protocol
|
|
|
|
|
|
class BridgeTransport(Protocol):
|
|
def send(
|
|
self, request: Mapping[str, Any], *, token: str, endpoint: str
|
|
) -> Mapping[str, Any] | None: ...
|
|
|
|
|
|
class StdioBridge:
|
|
"""Translate bounded JSON-lines messages to remote MCP without shell or token output."""
|
|
|
|
ALLOWED_METHODS = frozenset(
|
|
{
|
|
"initialize",
|
|
"notifications/initialized",
|
|
"ping",
|
|
"tools/list",
|
|
"tools/call",
|
|
"resources/list",
|
|
"resources/templates/list",
|
|
"resources/read",
|
|
"prompts/list",
|
|
"prompts/get",
|
|
}
|
|
)
|
|
|
|
def __init__(self, *, endpoint: str, token_env: str | None = None) -> None:
|
|
if not endpoint.startswith("https://"):
|
|
raise ValueError("stdio bridge endpoints must use HTTPS")
|
|
self.endpoint = endpoint
|
|
self.token_env = token_env or "ITWORX_MCP_ACCESS_TOKEN"
|
|
|
|
def forward(
|
|
self,
|
|
lines: Iterable[str],
|
|
transport: BridgeTransport,
|
|
*,
|
|
environ: Mapping[str, str] | None = None,
|
|
token_provider: Callable[[], str] | None = None,
|
|
) -> Iterator[str]:
|
|
values = environ if environ is not None else os.environ
|
|
static_token = values.get(self.token_env)
|
|
if not static_token and token_provider is None:
|
|
yield _error("authentication_required")
|
|
return
|
|
for raw in lines:
|
|
if len(raw.encode("utf-8")) > 4 * 1024 * 1024:
|
|
yield _error("request_too_large")
|
|
continue
|
|
try:
|
|
request = json.loads(raw)
|
|
except json.JSONDecodeError:
|
|
yield _error("invalid_json")
|
|
continue
|
|
if not isinstance(request, dict) or request.get("method") not in self.ALLOWED_METHODS:
|
|
yield _error("unsupported_method")
|
|
continue
|
|
try:
|
|
token = token_provider() if token_provider is not None else static_token
|
|
if not token:
|
|
yield _error("authentication_required")
|
|
continue
|
|
result = transport.send(request, token=token, endpoint=self.endpoint)
|
|
if result is not None:
|
|
yield json.dumps(dict(result), separators=(",", ":"))
|
|
except Exception:
|
|
yield _error("upstream_unavailable")
|
|
del static_token
|
|
|
|
|
|
def _error(code: str) -> str:
|
|
return json.dumps({"error": {"code": code}}, separators=(",", ":"))
|