Skip to content

OpenCode compatibility issues: local MCP handshake times out and ingest_traces schema is rejected #78

Closed

Description

@heraque

Summary

codebase-memory-mcp v0.5.3 works in Codex, but it does not work out of the box in OpenCode.

I was able to make it work in OpenCode only by adding a local wrapper around the binary. There seem to be two separate compatibility problems:

  1. OpenCode finds the server, starts it, but times out during MCP tool discovery (Failed to get tools)
  2. Even after the server is made reachable, OpenCode rejects the ingest_traces tool schema because traces is declared as an array without items

Environment

  • macOS
  • OpenCode 1.2.27
  • codebase-memory-mcp local binary installed as:
    • /Users/<user>/.local/bin/codebase-memory-mcp
  • OpenCode configured as a local MCP server

Expected behavior

OpenCode should be able to:

  • load codebase-memory-mcp as a local MCP server
  • successfully complete the MCP handshake
  • list the server tools
  • use the tools without schema validation failures

Actual behavior

OpenCode initially failed in two ways:

1. MCP startup / tool discovery timeout

OpenCode log:

service=mcp key=codebase-memory-mcp type=local found
...
ERROR service=mcp key=codebase-memory-mcp error=Operation timed out after 30000ms failed to get tools from client

Activity

  1. bingh0 commented on Mar 20, 2026

    @bingh0
    Contributor

    Try my PR - I encountered a similar issue with VS Code for the ingest_traces.

  2. heraque commented on Mar 20, 2026

    @heraque
    Author

    @bingh0 I’ll test your PR, but there is still an issue with the MCP not working in OpenCode due to a "local transport / handshake mismatch", where I had to create a wrapper in front of it so OpenCode would accept the MCP connection.

  3. DeusData commented on Mar 20, 2026

    @DeusData
    Owner

    The ingest_traces schema issue (problem #2) is fixed — we added "items":{"type":"object"} to the array schema in 89970f2 and merged the same fix again in PR #79 (92275ff).

    The MCP protocol version negotiation is also fixed — the server now supports versions 2025-11-25, 2025-06-18, 2025-03-26, and 2024-11-05 via proper version negotiation (83619f8, refined in PR #79).

    For the handshake timeout (problem #1): could you test with the latest build from main and let us know if it's resolved? If the timeout persists, we'd need more details about what OpenCode expects during the handshake.

  4. heraque commented on Mar 20, 2026

    @heraque
    Author

    @DeusData I tested the latest main build directly, without the wrapper.

    The fixes for protocol negotiation and ingest_traces schema are present, but OpenCode still does not work without the wrapper.

    The remaining issue appears to be transport compatibility:

    • when I talk to the server using raw JSON over stdio, it responds correctly
    • when I talk to the server using standard MCP stdio framing (Content-Length), it does not respond to initialize / tools/list

    This matches the OpenCode failure:

    Failed to get tools
    ERROR ... failed to get tools from client
    
  5. heraque commented on Mar 20, 2026

    @heraque
    Author

    @DeusData I think the remaining issue is in src/mcp/mcp.c, inside cbm_mcp_server_run(...).

    The current event loop still reads stdin as line-delimited JSON and writes responses as plain JSON + newline:

    if (cbm_getline(&line, &cap, in) <= 0) {
        break;
    }
    ...
    (void)fprintf(out, "%s\n", resp);

    This is likely the reason OpenCode still fails: it expects standard MCP stdio framing (Content-Length), while the server loop still behaves like raw JSON-line stdio.

    The likely fix is to make cbm_mcp_server_run(...) support framed MCP stdio requests/responses instead of only newline-delimited JSON.

  6. DeusData commented on Mar 20, 2026

    @DeusData
    Owner

    @heraque Fixed in 8ca7199 — the server now auto-detects Content-Length framed transport (LSP-style).

    When a client sends Content-Length: <n>\r\n\r\n<json>, the server reads the header, skips the blank separator, reads the exact payload, and responds with Content-Length framing. Regular JSONL clients (Claude Code, Cursor, etc.) are unaffected.

    This will ship with v0.5.4. Would appreciate if you can test with a build from main to confirm OpenCode works without the wrapper now.

  7. DeusData commented on Mar 20, 2026

    @DeusData
    Owner

    @heraque This is exactly what we just shipped in 8ca7199 (a few minutes ago). The cbm_mcp_server_run() event loop now auto-detects Content-Length framing:

    • Input: if a line starts with Content-Length:, reads the header, skips the blank separator, reads the exact payload
    • Output: responds with Content-Length: <n>\r\n\r\n<json>
    • Backwards compatible: bare JSONL clients still work — the detection only triggers on the Content-Length: prefix

    This is building in the dry run right now. Once v0.5.4 ships you can test without the wrapper. Or build from main to test immediately.

  8. added a commit that references this issue on Mar 21, 2026
    8ca7199
  9. trollkotze commented on Mar 21, 2026

    @trollkotze

    @DeusData I still have the same error after building from newest main (aa2b60b)

  10. DeusData commented on Mar 21, 2026

    @DeusData
    Owner

    @trollkotze will check soon, likely in the next days. I need first to finish the new security layers in the pipeline. Security has currently my highest priority before adding new features, as the project is already relatively complex and I need to make sure that security for everyone is given. Will update here once time was there to check it. Please a bit of patience. If you might find the issue, feel free to make a PR, or check if someone already tried to fix it :)

  11. pinned and unpinned a comment on Mar 21, 2026
  12. DeusData commented on Mar 21, 2026

    @DeusData
    Owner

    Fixed in v0.5.5: Added Content-Length framed transport for OpenCode compatibility, and fixed the ingest_traces array schema (items property was missing). The server now auto-detects transport mode — bare JSONL for Claude Code, Content-Length framing for OpenCode and similar LSP-style clients.

  13. trollkotze commented on Mar 22, 2026

    @trollkotze

    @DeusData Thank you. Of course, I'll try. And no problem. Patience is good for everyone. Good to slow down a bit when AI is rushing us all now to produce more stuff quickly, which we can do now even without understanding it completely. :)

    I just tried after building from the newest commit. Still same error in opencode.

    It creates ~/.cache/codebase-memory-mcp/ on first run, 12KB in size.

    In opencode I see "codebase-memory-mcp Failed to get tools" marked with red dot for error under the MCP section in the sidebar.

    In my opencode.jsonc I have this:

      "mcp": {
        "codebase-memory-mcp": {
          "type": "local",
          "command": ["/home/trollkotze/vcwork/codebase-memory-mcp/codebase-memory-mcp.sh"],
          "enabled": true,
          "timeout": 30000
        }
      },
    

    Anything wrong about that config?

    That .sh script is just a logging wrapper so I could see that output above from codebase-memory-mcp:

    #!/bin/bash
    /home/trollkotze/vcwork/codebase-memory-mcp/build/c/codebase-memory-mcp | tee 2>&1 /tmp/codebase-memory-mcp.log
    

    Here is what is logged by codebase-memory-mcp:

    {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-11-25","serverInfo":{"name":"codebase-memory-mcp","version":"0.10.0"},"capabilities":{"tools":{}}}}
    {"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"index_repository","description":"Index a repository into the knowledge graph","inputSchema":{"type":"object","properties":{"repo_path":{"type":"string","description":"Path to the repository"},"mode":{"type":"string","enum":["full","fast"],"default":"full"}},"required":["repo_path"]}},{"name":"search_graph","description":"Search the code knowledge graph for functions, classes, routes, and variables. Use INSTEAD OF grep/glob when finding code definitions, implementations, or relationships. Returns precise results in one call.","inputSchema":{"type":"object","properties":{"project":{"type":"string"},"label":{"type":"string"},"name_pattern":{"type":"string"},"qn_pattern":{"type":"string"},"file_pattern":{"type":"string"},"relationship":{"type":"string"},"min_degree":{"type":"integer"},"max_degree":{"type":"integer"},"exclude_entry_points":{"type":"boolean"},"include_connected":{"type":"boolean"},"limit":{"type":"integer","description":"Max results. Default: unlimited"},"offset":{"type":"integer","default":0}}}},{"name":"query_graph","description":"Execute a Cypher query against the knowledge graph for complex multi-hop patterns, aggregations, and cross-service analysis.","inputSchema":{"type":"object","properties":{"query":{"type":"string","description":"Cypher query"},"project":{"type":"string"},"max_rows":{"type":"integer","description":"Optional row limit. Default: unlimited (100k ceiling)"}},"required":["query"]}},{"name":"trace_call_path","description":"Trace function call paths — who calls a function and what it calls. Use INSTEAD OF grep when finding callers, dependencies, or impact analysis.","inputSchema":{"type":"object","properties":{"function_name":{"type":"string"},"project":{"type":"string"},"direction":{"type":"string","enum":["inbound","outbound","both"],"default":"both"},"depth":{"type":"integer","default":3},"edge_types":{"type":"array","items":{"type":"string"}}},"required":["function_name"]}},{"name":"get_code_snippet","description":"Read source code for a function/class/symbol. IMPORTANT: First call search_graph to find the exact qualified_name, then pass it here. This is a read tool, not a search tool. Accepts full qualified_name (exact match) or short function name (returns suggestions if ambiguous).","inputSchema":{"type":"object","properties":{"qualified_name":{"type":"string","description":"Full qualified_name from search_graph, or short function name"},"project":{"type":"string"},"include_neighbors":{"type":"boolean","default":false}},"required":["qualified_name"]}},{"name":"get_graph_schema","description":"Get the schema of the knowledge graph (node labels, edge types)","inputSchema":{"type":"object","properties":{"project":{"type":"string"}}}},{"name":"get_architecture","description":"Get high-level architecture overview — packages, services, dependencies, and project structure at a glance.","inputSchema":{"type":"object","properties":{"project":{"type":"string"},"aspects":{"type":"array","items":{"type":"string"}}}}},{"name":"search_code","description":"Search source code content with text or regex patterns. Use for string literals, error messages, and config values that are not in the knowledge graph.","inputSchema":{"type":"object","properties":{"pattern":{"type":"string"},"project":{"type":"string"},"file_pattern":{"type":"string"},"regex":{"type":"boolean","default":false},"limit":{"type":"integer","description":"Max results. Default: unlimited"}},"required":["pattern"]}},{"name":"list_projects","description":"List all indexed projects","inputSchema":{"type":"object","properties":{}}},{"name":"delete_project","description":"Delete a project from the index","inputSchema":{"type":"object","properties":{"project_name":{"type":"string"}},"required":["project_name"]}},{"name":"index_status","description":"Get the indexing status of a project","inputSchema":{"type":"object","properties":{"project":{"type":"string"}}}},{"name":"detect_changes","description":"Detect code changes and their impact","inputSchema":{"type":"object","properties":{"project":{"type":"string"},"scope":{"type":"string"},"depth":{"type":"integer","default":2},"base_branch":{"type":"string","default":"main"}}}},{"name":"manage_adr","description":"Create or update Architecture Decision Records","inputSchema":{"type":"object","properties":{"project":{"type":"string"},"mode":{"type":"string","enum":["get","update","sections"]},"content":{"type":"string"},"sections":{"type":"array","items":{"type":"string"}}}}},{"name":"ingest_traces","description":"Ingest runtime traces to enhance the knowledge graph","inputSchema":{"type":"object","properties":{"traces":{"type":"array","items":{"type":"object"}},"project":{"type":"string"}},"required":["traces"]}}]}}
    

    I will ask my AI slaves to figure this out and explain it to me and report back later when I know how to fix it.

  14. trollkotze commented on Mar 22, 2026

    @trollkotze

    @DeusData

    Okay, here is what my AI slave came up with, re the "Failed to get tools" error in OpenCode:

    Look at the event loop:

    struct pollfd pfd = {.fd = fd, .events = POLLIN};  
    int pr = poll(&pfd, 1, STORE_IDLE_TIMEOUT_S * 1000);  // 60 second timeout  
    if (pr == 0) {  
      cbm_mcp_server_evict_idle(srv, STORE_IDLE_TIMEOUT_S);  
      continue;  // ← goes back to poll()  
    }  
    if (cbm_getline(&line, &cap, in) <= 0) {  
      break;  
    }  
    

    Here's the exact sequence:

    1. Server processes initialize, writes response, loops back to poll()
    2. The notifications/initialized and tools/list are already in the FILE* internal buffer (libc read them during the previous getline() call)
    3. poll() checks the raw fd — the kernel pipe buffer is empty (libc already consumed those bytes into its userspace buffer)
    4. poll() blocks for 60 seconds
    5. After 60 seconds, poll() returns 0 (timeout), the loop calls evict_idle() and does continue — back to poll()
    6. OpenCode's cancellation notification arrives on the fd at the 60-second mark
    7. NOW poll() wakes up, getline() reads from the FILE* buffer — but it reads the buffered notifications/initialized first (from step > 2), processes it (returns NULL, no response), loops back
    8. Next getline() reads the buffered tools/list (from step 2), processes it, writes the response
    9. The response arrives at 14:10:28.654 — 60.003 seconds after the request
      This is a confirmed bug in cbm_mcp_server_run(). Mixing poll() on a raw fd with getline() on a buffered FILE* is broken when the client sends multiple messages in quick succession. The FILE* buffer swallows data that poll() can't see.
      The fix is straightforward — before calling poll(), check if the FILE* has buffered data. On glibc (your Linux Mint), you can do this with __freadable() or more portably by checking if there's pending input. The simplest portable fix:
      Replace the poll() section (lines 2392–2402) with something like:
    /* Check if FILE* has buffered data before polling the raw fd.  
     * getline() may have read ahead, leaving data in the buffer  
     * that poll() can't see. */  
    int has_buffered = (in->_IO_read_ptr < in->_IO_read_end);  // glibc-specific  
    if (!has_buffered) {  
        struct pollfd pfd = {.fd = fd, .events = POLLIN};  
        int pr = poll(&pfd, 1, STORE_IDLE_TIMEOUT_S * 1000);  
        if (pr < 0) break;  
        if (pr == 0) {  
            cbm_mcp_server_evict_idle(srv, STORE_IDLE_TIMEOUT_S);  
            continue;  
        }  
    }  
    

    Or more portably, just skip poll() entirely and use a non-blocking read approach — but that's a bigger refactor.
    You should report this back on the GitHub issue with the wire log. The 60-second gap is the smoking gun.

    My own understanding here (and attention span to deepen it) is quite limited, so don't know if that solution is completely solid. But for me it works.
    I'll make a PR.

    Oh, and regardng glibc, there was some issue in the Makefile as well for me at first, failing to compile because of some GNU-related flags missing or something. (I'm on Linux Mint 21.2.) Not really versed in that stuff either. So I applied some fix that my AI slave gave me to compile on Linux. Not sure if that will break something on MacOS or other operating systems. I'll open a separate issue for that.

  15. added a commit that references this issue on Apr 8, 2026
    ec29f4a
  16. added a commit that references this issue on May 20, 2026
    5fd1ced
  17. added a commit that references this issue on May 23, 2026
    d501b5f
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

    editor/integrationEditor compatibility and CLI integration

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions