Skip to content

Reasons for Google Antigravity MCP call failure and temporary solutions #65

Description

@oy3o

Problem

The initial configuration using nc to bridge the MCP stdio transport to the CogServer's TCP port (18888) failed with invalid request errors.
This was caused by two issues:

  1. Buffering: Standard nc or shell pipes can buffer input/output, breaking the strict line-based JSON-RPC expectation of the CogServer.
  2. Protocol Violation: The CogServer sends a non-compliant {"jsonrpc":"2.0","result":{}} response to the initialized notification (which should not have a response). This caused strict MCP clients to reject the connection.

Temporary Solution

We created a custom Python adapter script to replace nc.

stdio_tcp_client.py
#!/usr/bin/env python3
import socket
import sys
import threading
import argparse
import time

BUFFER_SIZE = 65536
# LOG_FILE = "/tmp/mcp_debug.log"

# def log(msg):
#     with open(LOG_FILE, "a") as f:
#         timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
#         f.write(f"[{timestamp}] {msg}\n")


def stdin_to_socket(sock):
    """Reads from stdin and writes to the socket."""
    # log("Starting stdin_to_socket thread")
    try:
        while True:
            # Read from stdin
            try:
                # Use read1 to get available bytes immediately
                chunk = sys.stdin.buffer.read1(4096)
            except AttributeError:
                chunk = sys.stdin.buffer.read(4096)

            if not chunk:
                # log("Stdin EOF received")
                break

            # log(f"Stdin -> Socket ({len(chunk)} bytes): {chunk}")
            try:
                sock.sendall(chunk)
            except Exception as e:
                # log(f"Socket send error: {e}")
                break

    except Exception as e:
        pass  # log(f"Stdin loop error: {e}")
    finally:
        # log("Shutting down socket WR")
        try:
            sock.shutdown(socket.SHUT_WR)
        except:
            pass


def socket_to_stdout(sock):
    """Reads from the socket and writes to stdout, filtering bad protocol messages."""
    # log("Starting socket_to_stdout thread")
    buffer = b""
    try:
        while True:
            data = sock.recv(BUFFER_SIZE)
            if not data:
                # log("Socket EOF received")
                break

            # log(f"Socket -> Buffer ({len(data)} bytes): {data}")
            buffer += data

            while b"\n" in buffer:
                line_end = buffer.find(b"\n") + 1
                line = buffer[:line_end]
                buffer = buffer[line_end:]

                # Check for the specific malformed response to 'notifications/initialized'
                # CogServer sends: {"jsonrpc":"2.0","result":{}}
                # This response has no 'id', which violates JSON-RPC 2.0 for a Response object,
                # and it shouldn't exist because 'initialized' is a Notification.
                if b'"result":{}' in line and b'"id"' not in line:
                    # log(f"DROPPING MALFORMED LINE: {line}")
                    continue

                # log(f"Forwarding Line: {line}")
                sys.stdout.buffer.write(line)
                sys.stdout.buffer.flush()

    except Exception as e:
        sys.stderr.write(f"Socket loop error: {e}\n")


def main():
    parser = argparse.ArgumentParser(description="Bridge Stdio to TCP for MCP")
    parser.add_argument("host", help="Hostname", nargs="?", default="localhost")
    parser.add_argument("port", help="Port", type=int, nargs="?", default=18888)
    args = parser.parse_args()

    # log(f"=== Starting Session connecting to {args.host}:{args.port} ===")

    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect((args.host, args.port))
        # log("Socket connected successfully")
    except Exception as e:
        # log(f"Failed to connect: {e}")
        sys.stderr.write(f"Failed to connect: {e}\n")
        sys.exit(1)

    t = threading.Thread(target=socket_to_stdout, args=(sock,), daemon=True)
    t.start()

    stdin_to_socket(sock)

    t.join(timeout=2.0)
    # log("=== Session Ended ===")


if __name__ == "__main__":
    main()

Key Features:

  • Unbuffered I/O: Reads directly from sys.stdin.buffer and flushes sys.stdout.buffer immediately after every message.
  • Protocol Filtering: Intercepts and drops the malformed {"jsonrpc":"2.0","result":{}} message from CogServer, preventing client errors.
  • Robustness: Handles socket closures and thread synchronization cleanly.

Configuration

The mcp_config.json was updated to use this adapter:

{
  "mcpServers": {
    "atomese": {
      "command": "path/to/cogserver/examples/mcp/stdio_tcp_client.py",
      "args": [
        "localhost",
        "18888"
      ],
      "env": {}
    }
  }
}

Verification

  • Connectivity: Verified via manual nc tests and the Python script.
  • Functionality: Successfully executed atomese/version and atomese/echo tools.
  • Result:
    • CogServer Version: 5.2.0
    • Echo Test: Passed

While temporary solutions exist, modifying the server to satisfy more demanding clients might be a better option.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions