refactor: no comments left - one-line module docstrings, contracts on public fields only; jobs/job.py; example config and README
This commit is contained in:
@@ -1,22 +1,8 @@
|
||||
"""Tolerant transports for upstream MCPs that don't strictly speak JSON-RPC.
|
||||
|
||||
Some real-world MCP servers print non-JSON chatter to stdout before / between
|
||||
their actual JSON-RPC frames (``Processing...``, banners, dependency-load
|
||||
messages, etc.). The reference ``mcp.client.stdio.stdio_client`` parses every
|
||||
stdout line as JSON-RPC and ships any parse failure downstream as an
|
||||
exception, which the MCP ``ClientSession`` then logs as a warning that bleeds
|
||||
into client UIs (Cursor, Cline) when they connect through us.
|
||||
|
||||
``LenientStdioTransport`` re-implements the stdio-client wiring with one
|
||||
behavioural change: lines that don't parse as JSON-RPC are *silently
|
||||
dropped* (one ``DEBUG`` log entry, no exception forwarded). Downstream
|
||||
consumers see only valid messages. We keep the rest of the contract identical
|
||||
to the reference client, including the spec-mandated graceful shutdown
|
||||
sequence (close stdin → wait → SIGTERM → SIGKILL).
|
||||
|
||||
The transport plugs into ``fastmcp.server.create_proxy`` just like
|
||||
``StdioTransport`` does, so the rest of the aggregator doesn't need to
|
||||
know which flavour it got.
|
||||
Some MCP servers print non-JSON chatter to stdout (banners, dependency-load
|
||||
messages) that the reference client forwards as exceptions, which bleed into
|
||||
client UIs as warnings. This transport drops those lines instead.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -70,13 +56,15 @@ class LenientStdioTransport(ClientTransport):
|
||||
cwd: str | None = None,
|
||||
log_file: TextIO | None = None,
|
||||
) -> None:
|
||||
"""``log_file`` takes an already-open ``TextIO``.
|
||||
|
||||
Unlike the upstream transport, this one does not open a ``Path``
|
||||
for you.
|
||||
"""
|
||||
self.command = command
|
||||
self.args = args
|
||||
self.env = env
|
||||
self.cwd = cwd
|
||||
# TextIO only — pre-open Path callers themselves. The upstream
|
||||
# ``StdioTransport`` opens Path for you, but we keep this thin so
|
||||
# the type contract stays narrow and easy to validate.
|
||||
self.log_file = log_file
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
@@ -100,7 +88,7 @@ class LenientStdioTransport(ClientTransport):
|
||||
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
async def _lenient_stdio_client( # noqa: PLR0915 — mirrors mcp.client.stdio.stdio_client
|
||||
async def _lenient_stdio_client( # noqa: PLR0915
|
||||
server: StdioServerParameters, errlog: TextIO = sys.stderr
|
||||
) -> AsyncIterator[
|
||||
tuple[
|
||||
@@ -112,8 +100,8 @@ async def _lenient_stdio_client( # noqa: PLR0915 — mirrors mcp.client.stdio.s
|
||||
|
||||
All differences from upstream live in ``stdout_reader``: lines that fail
|
||||
``JSONRPCMessage.model_validate_json`` are logged at DEBUG and skipped,
|
||||
never forwarded as exceptions. This is what makes warning-noisy MCPs
|
||||
quiet from the consumer's point of view.
|
||||
never forwarded as exceptions. Shutdown still follows the MCP spec
|
||||
sequence: close stdin, wait, SIGTERM, then SIGKILL.
|
||||
"""
|
||||
read_stream_writer, read_stream = anyio.create_memory_object_stream[
|
||||
SessionMessage | Exception
|
||||
@@ -159,7 +147,7 @@ async def _lenient_stdio_client( # noqa: PLR0915 — mirrors mcp.client.stdio.s
|
||||
continue
|
||||
try:
|
||||
message = types.JSONRPCMessage.model_validate_json(stripped)
|
||||
except Exception: # noqa: BLE001 — by design, see module doc
|
||||
except Exception: # noqa: BLE001
|
||||
_log.debug(
|
||||
"lenient stdio: dropped non-JSON line: %r",
|
||||
stripped[:200],
|
||||
@@ -192,7 +180,6 @@ async def _lenient_stdio_client( # noqa: PLR0915 — mirrors mcp.client.stdio.s
|
||||
try:
|
||||
yield read_stream, write_stream
|
||||
finally:
|
||||
# MCP spec stdio shutdown: close stdin → wait → SIGTERM → SIGKILL.
|
||||
if process.stdin:
|
||||
with contextlib.suppress(Exception):
|
||||
await process.stdin.aclose()
|
||||
|
||||
Reference in New Issue
Block a user