You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The MCP server serves JSON-RPC over stdout
(src/osw/mcp/server.py:170, mcp.run(transport="stdio")).
The existing protection does not cover logging. Context.guard() wraps each
osw call in redirect_stdout(sys.stderr) when policy.capture_stdout is set
(src/osw/service/context.py:113-125, enabled at src/osw/mcp/server.py:134).
A logging.StreamHandler stores the stream object at construction time, so
rebinding sys.stdout afterwards has no effect on it.
Reproduction of the last point, independent of osw:
importioimportloggingimportsysfromcontextlibimportredirect_stdouthandler=logging.StreamHandler(sys.stdout) # built before the redirectlogger=logging.getLogger("probe")
logger.addHandler(handler)
logger.setLevel(logging.INFO)
logger.propagate=Falsebuffer=io.StringIO()
withredirect_stdout(buffer):
logger.info("does redirect_stdout capture this?")
print("captured by redirect:", repr(buffer.getvalue()))
The record is written to the real stdout and buffer stays empty.
Any osw log record emitted during a tool call is written to stdout while the
JSON-RPC stream is live. This is the case the guard was written to prevent.
Proposed fix
Redirect the osw logger to stderr in main() in src/osw/mcp/server.py, before mcp.run():
osw.enable_logging(stream=sys.stderr)
osw.disable_logging() is the alternative, if the server should stay silent
unless the client configures logging itself.
Neither PR can carry this alone: enable_logging only exists in #160, and src/osw/mcp/server.py only exists in #133. Whichever merges second should add
the call, plus a test asserting that a tool call writes nothing to stdout.
Related
The osw[wikitext] hint in src/osw/wiki_tools.py is the current instance of
the same pattern. It reaches stdout through print() today, but at import time
only, so it does not corrupt the stream. #160 converts it to a logger call,
which does not change its destination as long as the default handler writes to
stdout.
Once #160 and
#133 are both merged, osw log
records will be written to the JSON-RPC channel of the MCP stdio server.
Cause
enable_logging()at import time insrc/osw/__init__.py. Itsdefault stream is
sys.stdoutand its default level isINFO.(
src/osw/mcp/server.py:170,mcp.run(transport="stdio")).Context.guard()wraps eachosw call in
redirect_stdout(sys.stderr)whenpolicy.capture_stdoutis set(
src/osw/service/context.py:113-125, enabled atsrc/osw/mcp/server.py:134).A
logging.StreamHandlerstores the stream object at construction time, sorebinding
sys.stdoutafterwards has no effect on it.Reproduction of the last point, independent of osw:
The record is written to the real stdout and
bufferstays empty.Effect
OSW_LOG_LEVELis unset is written tostdout at import, before the protocol starts.
JSON-RPC stream is live. This is the case the guard was written to prevent.
Proposed fix
Redirect the osw logger to stderr in
main()insrc/osw/mcp/server.py, beforemcp.run():osw.disable_logging()is the alternative, if the server should stay silentunless the client configures logging itself.
Neither PR can carry this alone:
enable_loggingonly exists in #160, andsrc/osw/mcp/server.pyonly exists in #133. Whichever merges second should addthe call, plus a test asserting that a tool call writes nothing to stdout.
Related
The
osw[wikitext]hint insrc/osw/wiki_tools.pyis the current instance ofthe same pattern. It reaches stdout through
print()today, but at import timeonly, so it does not corrupt the stream. #160 converts it to a logger call,
which does not change its destination as long as the default handler writes to
stdout.