please consider temporary back-compat or clearer error; rmcp stdio client: handshake fails on legacy `notifications/message` ({level,message})

Resolved 💬 10 comments Opened Oct 25, 2025 by prime9dev Closed Jan 31, 2026
💡 Likely answer: A maintainer (gpeal, contributor) responded on this thread — see the highlighted reply below.

What feature would you like to see?

Improve interoperability for notifications/message during the MCP 2025-06-18 transition:

  • Temporary backwards-compatibility: accept both payload shapes for notifications/message — legacy { level, message } and current { level, data, logger? } — behind a short deprecation window or a config flag.
  • Or, at minimum, clearer diagnostics: when a server sends { level, message }, surface a precise error like<br>“MCP schema mismatch: notifications/message expected data, got message”<br>so server authors can fix quickly.

Either approach would make Codex CLI more forgiving or at least more actionable while servers across the ecosystem finish migrating.

Additional information

Summary<br>After Codex CLI switched STDIO to the Rust-based rmcp client (e.g., v0.49.0), it strictly validates MCP messages and closes the connection if a server still emits notifications/message with { level, message }. The current MCP spec (2025-06-18) uses { level, data, logger? }. Some popular servers in the ecosystem haven’t migrated yet, which causes immediate handshake failures in Codex.

  • Codex releases: [https://github.com/openai/codex/releases](<https://github.com/openai/codex/releases>)
  • MCP spec (Logging, 2025-06-18): [https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging](<https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging>)

Environment

  • Codex CLI: v0.49.0
  • OS: Ubuntu 24.04
  • Example server: nx-mcp (latest), STDIO transport

Minimal repro<br>Start any MCP server that emits notifications/message with { level, message } (legacy). Connect with Codex CLI (STDIO). Codex fails the handshake.

Observed error (Codex CLI excerpt)

Failed to parse message decode: {"method":"notifications/message","params":{"level":"info","message":"..."},"jsonrpc":"2.0"}
Error: data did not match any variant of untagged enum JsonRpcMessage
input stream terminated

Concrete example of the legacy notification (from server STDIO)

{ "jsonrpc": "2.0", "method": "notifications/message", "params": { "level": "info", "message": "..." } }

Why this matters

  • The ecosystem contains multiple MCP servers that still send {message}. Codex currently fails fast and the error doesn’t point to the exact schema mismatch, making it harder for users to diagnose and server authors to fix.
  • MCP Inspector (JS) tends to be more tolerant, so users see a server “works in Inspector” but “fails in Codex,” which feels like a client-side regression.

Workaround I use today<br>A tiny STDIO shim that rewrites params.message → params.data on the fly restores interop immediately:

#!/usr/bin/env node
const { spawn } = require('node:child_process');
const readline = require('node:readline');
const workspace = process.argv[2] || process.cwd();

const child = spawn('npx', ['-y','nx-mcp@latest', workspace, '--transport','stdio','--disableTelemetry'],
  { stdio: ['pipe','pipe','pipe'] });

child.stderr.on('data', c => process.stderr.write(c));

const out = readline.createInterface({ input: child.stdout });
out.on('line', line => {
  let s = line;
  try {
    const msg = JSON.parse(line);
    if (msg?.method === 'notifications/message' && msg.params) {
      const p = msg.params;
      if (Object.prototype.hasOwnProperty.call(p,'message') && !('data' in p)) {
        msg.params = { ...p, data: p.message };
        delete msg.params.message;
        s = JSON.stringify(msg);
      }
    }
  } catch {}
  process.stdout.write(s + '\n');
});

const inp = readline.createInterface({ input: process.stdin });
inp.on('line', line => child.stdin.write(line + '\n'));

['SIGINT','SIGTERM','SIGHUP'].forEach(sig => process.on(sig, () => child.kill(sig)));
child.on('exit', code => process.exit(code ?? 0));

Ask

  • Please consider a temporary alias for {message}{data} (guarded by a deprecation plan or a config toggle), or provide a highly specific error message pointing to the exact field mismatch. Either would reduce friction while servers upgrade to the 2025-06-18 schema.

View original on GitHub ↗

10 Comments

gpeal contributor · 8 months ago

Do you mind sharing the MCP spec that has "message" instead of "data"? The 2024-11-05 spec is the same as today. I'm not saying you're wrong but I'd like to refer to a spec for reference and complete understanding here.

https://modelcontextprotocol.io/specification/2024-11-05/server/utilities/logging#log-message-notifications

prime9dev · 8 months ago
Do you mind sharing the MCP spec that has "message" instead of "data"? The 2024-11-05 spec is the same as today. I'm not saying you're wrong but I'd like to refer to a spec for reference and complete understanding here. https://modelcontextprotocol.io/specification/2024-11-05/server/utilities/logging#log-message-notifications

Thanks for the clarification, you’re right. I wasn’t claiming there was a spec version that used message. This is about interoperability: some servers (e.g., nx-mcp over STDIO) still emit notifications/message with {level, message}, and the Codex (rmcp stdio client) closes the connection during the handshake.

Example of what I receive from the server (STDIO) and what Codex logs:

# server notification as received over STDIO
{"jsonrpc":"2.0","method":"notifications/message",
 "params":{"level":"info","message":"Using local Nx package at /path/to/workspace/node_modules/nx/src/daemon/client/client.js"}}

# Codex error (excerpt)
Failed to parse message decode: {"method":"notifications/message","params":{"level":"info","message":"..."},"jsonrpc":"2.0"}
Error: data did not match any variant of untagged enum JsonRpcMessage
input stream terminated

My suggestion: surface a more precise diagnostic, because I currently just see “transport closed” on startup. A message like notifications/message: expected params.data, got params.message would make it much easier for users and server authors while the ecosystem fully converges on the spec.

gpeal contributor · 8 months ago

Got it. I'll leave this open to improve the error message rather than to update any actual compatibility. Sound good?

prime9dev · 8 months ago
Got it. I'll leave this open to improve the error message rather than to update any actual compatibility. Sound good?

It sounds incredible.

totaland · 8 months ago

mcp now can connect and discover tool in 0.52 version however the actual tool call still failed

   Caused by:
          tools/call failed: Transport closed
totaland · 8 months ago
prime9dev · 8 months ago
mcp now can't connect exact same issue #6020, #5619

99% of these issues can be resolved with the solution I attached in the issue itself. Check the codex-tui logs and try to adapt the answer to something suitable for rust-sdk. I just don't think this is a problem that the codex-cli developers should be solving; it's more likely that the creators of mcp are writing the contracts incorrectly. I can't speak with certainty, but rather express my perspective. That's how I solve problems like these.

so, after create a shim just configure it in .codex/config.toml like this:

# === MCP servers ===

[mcp_servers.nx-mcp]
command = "node"
args = ["/home/username/.codex/shims/nx-mcp-stdio-shim.js",
        "/home/username/path/to/yourworkspace"]
cwd = "/home/username/path/to/yourworkspace"
startup_timeout_sec = 120
tool_timeout_sec    = 300
u1-liquid · 8 months ago

So... is anyone working on this issue?

I also can’t use the latest VS Code extension because of this problem, so I’ve been stuck on 0.4.26.
(If I update to 0.4.30 or later, MCP servers fails to initialize and/or calls sometimes get “Transport closed” mid-call, making it far too unstable to use.)

Because the rmcp client even reacts to ordinary node's console.log outputs and causes a “transport closed”, I don’t think this is a problem that everyone should have to work around by writing shims.

totaland · 8 months ago

I found the real issue. I use third party library and it log to stdout while I run the mcp server.

import { inspect } from "node:util";

type StdoutConsoleMethod = "log" | "info" | "debug";

const STDOUT_METHODS: StdoutConsoleMethod[] = ["log", "info", "debug"];

let stdoutSanitized = false;

function formatValue(value: unknown): string {
	if (typeof value === "string") {
		return value;
	}

	try {
		return inspect(value, { depth: 5, breakLength: 120, compact: false });
	} catch {
		return String(value);
	}
}

/**
 * MCP STDIO transports expect stdout to contain JSON-RPC only.
 * Third-party libraries (like @ast-grep) occasionally log to stdout,
 * which corrupts the stream. This helper re-routes console.log/info/debug
 * to stderr so MCP clients keep a clean transport channel.
 */
export function ensureMcpStdoutIsQuiet(): void {
	if (stdoutSanitized || process.env.MCP_ALLOW_STDOUT_LOGS === "true") {
		return;
	}

	stdoutSanitized = true;

	for (const method of STDOUT_METHODS) {
		const original = console[method].bind(console);
		console[method] = ((...args: unknown[]) => {
			if (process.env.MCP_ALLOW_STDOUT_LOGS === "true") {
				original(...args);
				return;
			}

			if (args.length === 0) {
				return;
			}

			const message = args.map(formatValue).join(" ");
			process.stderr.write(`${message}\n`);
		}) as typeof console[StdoutConsoleMethod];
	}
}

call ensureMcpStdoutIsQuiet() before you start your mcp server will fix the issue.

etraut-openai contributor · 5 months ago

This feature request has received few upvotes in the past three months, so I'm going to close it.