You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
OpenCode compatibility issues: local MCP handshake times out and ingest_traces schema is rejected #78
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:
OpenCode finds the server, starts it, but times out during MCP tool discovery (Failed to get tools)
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
@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.
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.
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.
@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.
@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.
@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 :)
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.
@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.
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.
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:
Server processes initialize, writes response, loops back to poll()
The notifications/initialized and tools/list are already in the FILE* internal buffer (libc read them during the previous getline() call)
poll() checks the raw fd — the kernel pipe buffer is empty (libc already consumed those bytes into its userspace buffer)
poll() blocks for 60 seconds
After 60 seconds, poll() returns 0 (timeout), the loop calls evict_idle() and does continue — back to poll()
OpenCode's cancellation notification arrives on the fd at the 60-second mark
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
Next getline() reads the buffered tools/list (from step 2), processes it, writes the response
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.
Activity
bingh0 commentedon Mar 20, 2026
Try my PR - I encountered a similar issue with VS Code for the
ingest_traces.heraque commentedon Mar 20, 2026
@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.DeusData commentedon Mar 20, 2026
The
ingest_tracesschema 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, and2024-11-05via 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.
heraque commentedon Mar 20, 2026
@DeusData I tested the latest
mainbuild directly, without the wrapper.The fixes for protocol negotiation and
ingest_tracesschema are present, but OpenCode still does not work without the wrapper.The remaining issue appears to be transport compatibility:
Content-Length), it does not respond toinitialize/tools/listThis matches the OpenCode failure:
heraque commentedon Mar 20, 2026
@DeusData I think the remaining issue is in
src/mcp/mcp.c, insidecbm_mcp_server_run(...).The current event loop still reads stdin as line-delimited JSON and writes responses as plain JSON + newline:
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.
DeusData commentedon Mar 20, 2026
@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.
DeusData commentedon Mar 20, 2026
@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:Content-Length:, reads the header, skips the blank separator, reads the exact payloadContent-Length: <n>\r\n\r\n<json>Content-Length:prefixThis 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.
Add Content-Length framed transport support (OpenCode compatibility)
trollkotze commentedon Mar 21, 2026
@DeusData I still have the same error after building from newest main (aa2b60b)
DeusData commentedon Mar 21, 2026
@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 :)
DeusData commentedon Mar 21, 2026
Fixed in v0.5.5: Added Content-Length framed transport for OpenCode compatibility, and fixed the
ingest_tracesarray schema (itemsproperty was missing). The server now auto-detects transport mode — bare JSONL for Claude Code, Content-Length framing for OpenCode and similar LSP-style clients.trollkotze commentedon Mar 22, 2026
@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.jsoncI have this:Anything wrong about that config?
That .sh script is just a logging wrapper so I could see that output above from
codebase-memory-mcp:Here is what is logged by codebase-memory-mcp:
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.
trollkotze commentedon Mar 22, 2026
@DeusData
Okay, here is what my AI slave came up with, re the "Failed to get tools" error in OpenCode:
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.
Add Content-Length framed transport support (OpenCode compatibility)
Add Content-Length framed transport support (OpenCode compatibility)
Add Content-Length framed transport support (OpenCode compatibility)