Skip to content

osw-mcp writes its startup report to stderr with the locale encoding, not UTF-8 #193

Closed

Description

@LukasGold

src/osw/mcp/server.py writes its startup diagnostics to sys.stderr without setting the encoding. An MCP client starts the server with stderr connected to a pipe, so Python encodes it with the locale encoding. On a German Windows system that is cp1252, and a non-ASCII character in the report reaches the client's log as bytes it cannot decode.

This is the same defect as #187, which was fixed for the osw CLI only.

Affected writes

  • src/osw/mcp/server.py:167 sys.stderr.write(report.getvalue()) on a failed start
  • src/osw/mcp/server.py:170 print(f"[osw-mcp] failed to start: {exc}", file=sys.stderr, flush=True)
  • src/osw/mcp/server.py:174 sys.stderr.write(report.getvalue()) when OSW_VERBOSE is set

report holds the configuration report from config.log_config_sources, which prints the credential file path (src/osw/service/config.py:509) and the env file path (src/osw/service/config.py:530). A user account or directory named Müller is enough to trigger this.

Not affected

The JSON-RPC channel. The MCP SDK's stdio_server re-wraps the binary buffer as UTF-8 explicitly, with a comment that the platform encodings of the standard handles are unreliable. The protocol stream does not depend on the locale.

Severity

Low. sys.stderr uses the backslashreplace error handler by default, so a character the locale encoding cannot represent is mangled rather than raising UnicodeEncodeError. The result is a wrong path in a client log file, not a crash and not a broken protocol.

Suggested fix

The CLI solves this in _force_utf8_output() (src/osw/cli/main.py:40), called from the typer app callback. The MCP server has no equivalent. Both console scripts now pass through src/osw_entry.py, which is a natural place to do this once for osw and osw-mcp alike.

One note for whoever implements it: reconfiguring sys.stderr alone is sufficient for this defect. Reconfiguring sys.stdout in the MCP process would need checking against the SDK's file-descriptor claiming first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions