Skip to content

Commit 8ca7199

Browse files
committed
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.
1 parent f7315b9 commit 8ca7199

1 file changed

Lines changed: 44 additions & 1 deletion

File tree

src/mcp/mcp.c

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2406,7 +2406,7 @@ int cbm_mcp_server_run(cbm_mcp_server_t *srv, FILE *in, FILE *out) {
24062406
break;
24072407
}
24082408

2409-
/* Trim trailing newline */
2409+
/* Trim trailing newline/CR */
24102410
size_t len = strlen(line);
24112411
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
24122412
line[--len] = '\0';
@@ -2415,6 +2415,49 @@ int cbm_mcp_server_run(cbm_mcp_server_t *srv, FILE *in, FILE *out) {
24152415
continue;
24162416
}
24172417

2418+
/* Content-Length framing support (LSP-style transport).
2419+
* Some MCP clients (OpenCode, VS Code extensions) send:
2420+
* Content-Length: <n>\r\n\r\n<json>
2421+
* instead of bare JSONL. Detect the header, read the payload,
2422+
* and respond with the same framing. */
2423+
if (strncmp(line, "Content-Length:", 15) == 0) {
2424+
int content_len = (int)strtol(line + 15, NULL, 10);
2425+
if (content_len <= 0 || content_len > 10 * 1024 * 1024) {
2426+
continue; /* invalid or too large */
2427+
}
2428+
2429+
/* Skip blank line(s) between header and body */
2430+
while (cbm_getline(&line, &cap, in) > 0) {
2431+
size_t hlen = strlen(line);
2432+
while (hlen > 0 && (line[hlen - 1] == '\n' || line[hlen - 1] == '\r')) {
2433+
line[--hlen] = '\0';
2434+
}
2435+
if (hlen == 0) {
2436+
break; /* found the blank separator */
2437+
}
2438+
/* Skip other headers (e.g. Content-Type) */
2439+
}
2440+
2441+
/* Read exact content_len bytes */
2442+
char *body = malloc((size_t)content_len + 1);
2443+
if (!body) {
2444+
continue;
2445+
}
2446+
size_t nread = fread(body, 1, (size_t)content_len, in);
2447+
body[nread] = '\0';
2448+
2449+
char *resp = cbm_mcp_server_handle(srv, body);
2450+
free(body);
2451+
2452+
if (resp) {
2453+
size_t rlen = strlen(resp);
2454+
(void)fprintf(out, "Content-Length: %zu\r\n\r\n%s", rlen, resp);
2455+
(void)fflush(out);
2456+
free(resp);
2457+
}
2458+
continue;
2459+
}
2460+
24182461
char *resp = cbm_mcp_server_handle(srv, line);
24192462
if (resp) {
24202463
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)

0 commit comments

Comments
 (0)