Description
When the BM MCP server shuts down (e.g., after a benchmark run or when an MCP client disconnects), a ValueError: I/O operation on closed file traceback is thrown from the cloud promo system.
Stack trace
basic_memory/cli/app.py:58 in <lambda>
ctx.call_on_close(lambda: maybe_show_cloud_promo(ctx.invoked_subcommand))
basic_memory/cli/promo.py:85 in maybe_show_cloud_promo
interactive = _is_interactive_session() if is_interactive is None
basic_memory/cli/promo.py:27 in _is_interactive_session
return sys.stdin.isatty() and sys.stdout.isatty()
ValueError: I/O operation on closed file
Cause
maybe_show_cloud_promo is registered as a call_on_close callback. When the MCP server runs over stdio, stdin/stdout are closed by the time the callback fires. sys.stdin.isatty() then raises ValueError.
Suggested fix
Guard the isatty check:
def _is_interactive_session() -> bool:
try:
return sys.stdin.isatty() and sys.stdout.isatty()
except ValueError:
return False
Or skip the promo callback entirely when the invoked subcommand is mcp.
Reproduction
# Any MCP client that connects and disconnects will trigger this
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{},"clientInfo":{"name":"test","version":"1.0"},"protocolVersion":"2024-11-05"}}' | bm mcp --transport stdio
# Ctrl-C or close stdin → traceback appears
Environment
- basic-memory: installed from main via uv
- Python 3.13.12
- FastMCP 3.0.2
- macOS (x86_64)
Description
When the BM MCP server shuts down (e.g., after a benchmark run or when an MCP client disconnects), a
ValueError: I/O operation on closed filetraceback is thrown from the cloud promo system.Stack trace
Cause
maybe_show_cloud_promois registered as acall_on_closecallback. When the MCP server runs over stdio, stdin/stdout are closed by the time the callback fires.sys.stdin.isatty()then raisesValueError.Suggested fix
Guard the isatty check:
Or skip the promo callback entirely when the invoked subcommand is
mcp.Reproduction
Environment