Add Content-Length framed transport support (OpenCode compatibility)

Some MCP clients (OpenCode, some VS Code extensions) use LSP-style
Content-Length framing instead of bare JSONL:

  Content-Length: 123\r\n\r\n{"jsonrpc":"2.0",...}

The server now auto-detects the transport: if a line starts with
"Content-Length:", it reads the header, skips the blank separator,
reads the exact payload, and responds with Content-Length framing.
Regular JSONL clients are unaffected — the detection only triggers
on the Content-Length prefix.

Fixes #78.
This commit is contained in:
Martin Vogel
2026-03-20 21:22:06 +01:00
parent f7315b9bc5
commit 8ca7199847
+44 -1
View File
@@ -2406,7 +2406,7 @@ int cbm_mcp_server_run(cbm_mcp_server_t *srv, FILE *in, FILE *out) {
break;
}
/* Trim trailing newline */
/* Trim trailing newline/CR */
size_t len = strlen(line);
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
line[--len] = '\0';
@@ -2415,6 +2415,49 @@ int cbm_mcp_server_run(cbm_mcp_server_t *srv, FILE *in, FILE *out) {
continue;
}
/* Content-Length framing support (LSP-style transport).
* Some MCP clients (OpenCode, VS Code extensions) send:
* Content-Length: <n>\r\n\r\n<json>
* instead of bare JSONL. Detect the header, read the payload,
* and respond with the same framing. */
if (strncmp(line, "Content-Length:", 15) == 0) {
int content_len = (int)strtol(line + 15, NULL, 10);
if (content_len <= 0 || content_len > 10 * 1024 * 1024) {
continue; /* invalid or too large */
}
/* Skip blank line(s) between header and body */
while (cbm_getline(&line, &cap, in) > 0) {
size_t hlen = strlen(line);
while (hlen > 0 && (line[hlen - 1] == '\n' || line[hlen - 1] == '\r')) {
line[--hlen] = '\0';
}
if (hlen == 0) {
break; /* found the blank separator */
}
/* Skip other headers (e.g. Content-Type) */
}
/* Read exact content_len bytes */
char *body = malloc((size_t)content_len + 1);
if (!body) {
continue;
}
size_t nread = fread(body, 1, (size_t)content_len, in);
body[nread] = '\0';
char *resp = cbm_mcp_server_handle(srv, body);
free(body);
if (resp) {
size_t rlen = strlen(resp);
(void)fprintf(out, "Content-Length: %zu\r\n\r\n%s", rlen, resp);
(void)fflush(out);
free(resp);
}
continue;
}
char *resp = cbm_mcp_server_handle(srv, line);
if (resp) {
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)