Skip to main content

Keep your MCP server quiet on stdout — safe print/logging and a stdout guard so debug output can't corrupt the JSON-RPC stream.

Project description

quietmcp

Keep your MCP server quiet on stdout — print() and log freely without corrupting the JSON-RPC stream.

Python License

On an MCP stdio server, stdout is the protocol — every byte is part of the JSON-RPC stream the client parses. So the most natural thing in the world, a print("got here"), silently corrupts that stream and the client breaks with a baffling parse error. Worse, a third-party library you don't control can print a progress bar and take your server down with it.

The fix is mundane — send everything that isn't protocol to stderr — but easy to get wrong and easy for a dependency to undo. quietmcp makes it effortless:

import quietmcp

real_stdout = quietmcp.install()              # stray writes to stdout now go to stderr
log = quietmcp.get_logger("my-server")        # logs to stderr, never stdout
log.info("server starting")
quietmcp.safe_print("debug: tool called")     # print(), but safe

# hand the REAL stdout to your MCP transport for JSON-RPC:
run_stdio_server(stdout=real_stdout)

Now nothing — your code or your dependencies — can poison the protocol with a stray text write.


Install

pip install quietmcp

Zero dependencies, pure standard library.

The three pieces

1. safe_printprint() that goes to stderr

from quietmcp import safe_print
safe_print("about to call tool", tool_name)   # identical to print(), but on stderr

2. get_logger — a logger that can't reach stdout

log = quietmcp.get_logger("my-server", level=logging.DEBUG,
                          file="server.log", json=False, trace="trace.jsonl")

It logs to stderr (or a file), and — critically — sets propagate=False so your records never bubble up to a root logger whose handler might be pointed at stdout. It's idempotent, so calling it again with the same name won't stack duplicate handlers. Pass stream=sys.stdout and it refuses with a clear error: that's the bug it exists to prevent.

3. install() — guard stdout against everything

The piece that saves you from libraries you don't control. install() replaces sys.stdout with a guard and returns the real stream:

real_stdout = quietmcp.install(mode="redirect")   # default: stray writes -> stderr
mode a stray print() to stdout…
"redirect" (default) is written to stderr instead (you still see it)
"drop" is silently discarded
"strict" raises StdoutPollutionError — great for catching the offender in tests/CI

The guard only intercepts text writes. The binary layer (sys.stdout.buffer) — which the Python MCP SDK uses to emit JSON-RPC — passes straight through to the real stdout. So protocol output works untouched while print()/logging is contained. That's exactly the split you want, and it's why install() + the MCP SDK just work together.

Use it as a context manager when you'd rather scope it (e.g. in tests):

from quietmcp import protect_stdout, StdoutPollutionError

with protect_stdout("strict") as real_stdout:
    run_one_request()        # any stray stdout write in here raises

Note: this guards Python-level writes (print, logging, sys.stdout.write). A subprocess or a C extension writing to fd 1 directly is out of scope — those need OS-level redirection.

Capture and view a trace

Pass trace="…" to get_logger (or attach a TraceHandler) to tee structured records to a JSONL file, then read it back later — your own logs, kept, instead of scrolling off stderr:

log = quietmcp.get_logger("my-server", trace="trace.jsonl")
log.info("tool call", extra={"tool": "search", "ms": 12})
python -m quietmcp view trace.jsonl                  # readable table in the terminal
python -m quietmcp view trace.jsonl --level WARNING   # filter to WARNING and up
python -m quietmcp view trace.jsonl --html trace.html # a self-contained browsable page

Programmatically: read_trace, render_trace, and render_trace_html.

What it does not do

  • It doesn't speak MCP. It has no dependency on any MCP SDK and adds no protocol features — it just keeps your stdout clean so the SDK you already use keeps working.
  • It doesn't redirect OS file descriptors. Subprocesses/C libraries writing to fd 1 directly aren't intercepted (that needs os.dup2-level redirection).
  • It's not an observability platform. The trace viewer is a local convenience, not a hosted dashboard.

Part of an agent-reliability suite

quietmcp is the first of a small set of framework-agnostic tools for shipping agents and agent infrastructure you can trust. More are on the way.

Contributing

See CONTRIBUTING.md in the source distribution. The bar: zero dependencies, never write to stdout, and ship tests + docs with every change. MIT licensed.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

quietmcp-0.1.0.tar.gz (32.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

quietmcp-0.1.0-py3-none-any.whl (14.0 kB view details)

Uploaded Python 3

File details

Details for the file quietmcp-0.1.0.tar.gz.

File metadata

  • Download URL: quietmcp-0.1.0.tar.gz
  • Upload date:
  • Size: 32.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for quietmcp-0.1.0.tar.gz
Algorithm Hash digest
SHA256 3b9843f22c53df8f0b9ca096417c0499e0f42a20ec27f5f91c34336f8e87a75e
MD5 c6bd06161157ca3cfa9b3489b7c50c2a
BLAKE2b-256 d9f67a99ba5f310bd71039125513729e25d0e5557d50ee07cf5e69aa710534aa

See more details on using hashes here.

File details

Details for the file quietmcp-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: quietmcp-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 14.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for quietmcp-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d4c3359aee046758bc6f1e6f2d9a0f18e31c50ca0da07996057a286d317cf155
MD5 db85895abde29e01556cc77d5566702b
BLAKE2b-256 2ca6062311441476b22d03051dd8a2bce1784c196837db69289f7b3acef03a37

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page