Skip to content

Commit 92275ff

Browse files
committed
Merge PR #79: Fix VS Code compatibility (schema, install, protocol negotiation)
2 parents b5b9c6b + 83619f8 commit 92275ff

File tree

5 files changed

+137
-10
lines changed

5 files changed

+137
-10
lines changed

src/cli/cli.c

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -813,7 +813,7 @@ int cbm_remove_zed_mcp(const char *config_path) {
813813
/* ── Agent detection ──────────────────────────────────────────── */
814814

815815
cbm_detected_agents_t cbm_detect_agents(const char *home_dir) {
816-
cbm_detected_agents_t agents = {false, false, false, false, false, false, false, false};
816+
cbm_detected_agents_t agents = {false, false, false, false, false, false, false, false, false};
817817
if (!home_dir || !home_dir[0]) {
818818
return agents;
819819
}
@@ -874,6 +874,16 @@ cbm_detected_agents_t cbm_detect_agents(const char *home_dir) {
874874
agents.kilocode = true;
875875
}
876876

877+
/* VS Code: User config dir */
878+
#ifdef __APPLE__
879+
snprintf(path, sizeof(path), "%s/Library/Application Support/Code/User", home_dir);
880+
#else
881+
snprintf(path, sizeof(path), "%s/.config/Code/User", home_dir);
882+
#endif
883+
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
884+
agents.vscode = true;
885+
}
886+
877887
return agents;
878888
}
879889

@@ -2156,8 +2166,11 @@ int cbm_cmd_install(int argc, char **argv) {
21562166
if (agents.kilocode) {
21572167
printf(" KiloCode");
21582168
}
2169+
if (agents.vscode) {
2170+
printf(" VS-Code");
2171+
}
21592172
if (!agents.claude_code && !agents.codex && !agents.gemini && !agents.zed && !agents.opencode &&
2160-
!agents.antigravity && !agents.aider && !agents.kilocode) {
2173+
!agents.antigravity && !agents.aider && !agents.kilocode && !agents.vscode) {
21612174
printf(" (none)");
21622175
}
21632176
printf("\n\n");
@@ -2318,7 +2331,23 @@ int cbm_cmd_install(int argc, char **argv) {
23182331
printf(" instructions: %s\n", instr_path);
23192332
}
23202333

2321-
/* Step 12: Ensure PATH */
2334+
/* Step 12: Install VS Code */
2335+
if (agents.vscode) {
2336+
printf("VS Code:\n");
2337+
char config_path[1024];
2338+
#ifdef __APPLE__
2339+
snprintf(config_path, sizeof(config_path),
2340+
"%s/Library/Application Support/Code/User/mcp.json", home);
2341+
#else
2342+
snprintf(config_path, sizeof(config_path), "%s/.config/Code/User/mcp.json", home);
2343+
#endif
2344+
if (!dry_run) {
2345+
cbm_install_vscode_mcp(self_path, config_path);
2346+
}
2347+
printf(" mcp: %s\n", config_path);
2348+
}
2349+
2350+
/* Step 13: Ensure PATH */
23222351
char bin_dir[1024];
23232352
snprintf(bin_dir, sizeof(bin_dir), "%s/.local/bin", home);
23242353
const char *rc = cbm_detect_shell_rc(home);
@@ -2492,6 +2521,20 @@ int cbm_cmd_uninstall(int argc, char **argv) {
24922521
printf(" removed instructions\n");
24932522
}
24942523

2524+
if (agents.vscode) {
2525+
char config_path[1024];
2526+
#ifdef __APPLE__
2527+
snprintf(config_path, sizeof(config_path),
2528+
"%s/Library/Application Support/Code/User/mcp.json", home);
2529+
#else
2530+
snprintf(config_path, sizeof(config_path), "%s/.config/Code/User/mcp.json", home);
2531+
#endif
2532+
if (!dry_run) {
2533+
cbm_remove_vscode_mcp(config_path);
2534+
}
2535+
printf("VS Code: removed MCP config entry\n");
2536+
}
2537+
24952538
/* Step 2: Remove indexes */
24962539
int index_count = 0;
24972540
const char *cache_dir = get_cache_dir(home);

src/cli/cli.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ typedef struct {
113113
bool antigravity; /* ~/.gemini/antigravity/ exists */
114114
bool aider; /* aider on PATH */
115115
bool kilocode; /* KiloCode globalStorage dir exists */
116+
bool vscode; /* VS Code User config dir exists */
116117
} cbm_detected_agents_t;
117118

118119
/* Detect which coding agents are installed.

src/mcp/mcp.c

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,8 @@ static const tool_def_t TOOLS[] = {
305305
"\"sections\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}}}"},
306306

307307
{"ingest_traces", "Ingest runtime traces to enhance the knowledge graph",
308-
"{\"type\":\"object\",\"properties\":{\"traces\":{\"type\":\"array\"},\"project\":{\"type\":"
308+
"{\"type\":\"object\",\"properties\":{\"traces\":{\"type\":\"array\",\"items\":{\"type\":"
309+
"\"object\"}},\"project\":{\"type\":"
309310
"\"string\"}},\"required\":[\"traces\"]}"},
310311
};
311312

@@ -342,12 +343,44 @@ char *cbm_mcp_tools_list(void) {
342343
return out;
343344
}
344345

345-
char *cbm_mcp_initialize_response(void) {
346+
/* Supported protocol versions, newest first. The server picks the newest
347+
* version that it shares with the client (per MCP spec version negotiation). */
348+
static const char *SUPPORTED_PROTOCOL_VERSIONS[] = {
349+
"2025-11-25",
350+
"2025-06-18",
351+
"2025-03-26",
352+
"2024-11-05",
353+
};
354+
static const int SUPPORTED_VERSION_COUNT =
355+
(int)(sizeof(SUPPORTED_PROTOCOL_VERSIONS) / sizeof(SUPPORTED_PROTOCOL_VERSIONS[0]));
356+
357+
char *cbm_mcp_initialize_response(const char *params_json) {
358+
/* Determine protocol version: if client requests a version we support,
359+
* echo it back; otherwise respond with our latest. */
360+
const char *version = SUPPORTED_PROTOCOL_VERSIONS[0]; /* default: latest */
361+
if (params_json) {
362+
yyjson_doc *pdoc = yyjson_read(params_json, strlen(params_json), 0);
363+
if (pdoc) {
364+
yyjson_val *pv =
365+
yyjson_obj_get(yyjson_doc_get_root(pdoc), "protocolVersion");
366+
if (pv && yyjson_is_str(pv)) {
367+
const char *requested = yyjson_get_str(pv);
368+
for (int i = 0; i < SUPPORTED_VERSION_COUNT; i++) {
369+
if (strcmp(requested, SUPPORTED_PROTOCOL_VERSIONS[i]) == 0) {
370+
version = SUPPORTED_PROTOCOL_VERSIONS[i];
371+
break;
372+
}
373+
}
374+
}
375+
yyjson_doc_free(pdoc);
376+
}
377+
}
378+
346379
yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL);
347380
yyjson_mut_val *root = yyjson_mut_obj(doc);
348381
yyjson_mut_doc_set_root(doc, root);
349382

350-
yyjson_mut_obj_add_str(doc, root, "protocolVersion", "2024-11-05");
383+
yyjson_mut_obj_add_str(doc, root, "protocolVersion", version);
351384

352385
yyjson_mut_val *impl = yyjson_mut_obj(doc);
353386
yyjson_mut_obj_add_str(doc, impl, "name", "codebase-memory-mcp");
@@ -2302,7 +2335,7 @@ char *cbm_mcp_server_handle(cbm_mcp_server_t *srv, const char *line) {
23022335
char *result_json = NULL;
23032336

23042337
if (strcmp(req.method, "initialize") == 0) {
2305-
result_json = cbm_mcp_initialize_response();
2338+
result_json = cbm_mcp_initialize_response(req.params_raw);
23062339
start_update_check(srv);
23072340
detect_session(srv);
23082341
maybe_auto_index(srv);

src/mcp/mcp.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ char *cbm_mcp_text_result(const char *text, bool is_error);
5555
/* Format the tools/list response. Returns heap-allocated JSON. */
5656
char *cbm_mcp_tools_list(void);
5757

58-
/* Format the initialize response. Returns heap-allocated JSON. */
59-
char *cbm_mcp_initialize_response(void);
58+
/* Format the initialize response. params_json is the raw initialize params
59+
* (used for protocol version negotiation). Returns heap-allocated JSON. */
60+
char *cbm_mcp_initialize_response(const char *params_json);
6061

6162
/* ── Tool argument helpers ────────────────────────────────────── */
6263

tests/test_mcp.c

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,30 @@ TEST(jsonrpc_format_error) {
9898
* ══════════════════════════════════════════════════════════════════ */
9999

100100
TEST(mcp_initialize_response) {
101-
char *json = cbm_mcp_initialize_response();
101+
/* Default (no params): returns latest supported version */
102+
char *json = cbm_mcp_initialize_response(NULL);
102103
ASSERT_NOT_NULL(json);
103104
ASSERT_NOT_NULL(strstr(json, "codebase-memory-mcp"));
104105
ASSERT_NOT_NULL(strstr(json, "capabilities"));
105106
ASSERT_NOT_NULL(strstr(json, "tools"));
107+
ASSERT_NOT_NULL(strstr(json, "2025-11-25"));
108+
free(json);
109+
110+
/* Client requests a supported version: server echoes it */
111+
json = cbm_mcp_initialize_response("{\"protocolVersion\":\"2024-11-05\"}");
112+
ASSERT_NOT_NULL(json);
113+
ASSERT_NOT_NULL(strstr(json, "2024-11-05"));
114+
free(json);
115+
116+
json = cbm_mcp_initialize_response("{\"protocolVersion\":\"2025-06-18\"}");
117+
ASSERT_NOT_NULL(json);
118+
ASSERT_NOT_NULL(strstr(json, "2025-06-18"));
119+
free(json);
120+
121+
/* Client requests unknown version: server returns its latest */
122+
json = cbm_mcp_initialize_response("{\"protocolVersion\":\"9999-01-01\"}");
123+
ASSERT_NOT_NULL(json);
124+
ASSERT_NOT_NULL(strstr(json, "2025-11-25"));
106125
free(json);
107126
PASS();
108127
}
@@ -129,6 +148,35 @@ TEST(mcp_tools_list) {
129148
PASS();
130149
}
131150

151+
TEST(mcp_tools_array_schemas_have_items) {
152+
/* VS Code 1.112+ rejects array schemas without "items" (see
153+
* https://github.com/microsoft/vscode/issues/248810).
154+
* Walk every tool's inputSchema and verify that every "type":"array"
155+
* property also contains "items". */
156+
char *json = cbm_mcp_tools_list();
157+
ASSERT_NOT_NULL(json);
158+
159+
/* Scan for all occurrences of "type":"array" — each must be followed
160+
* by "items" before the next closing brace of that property. */
161+
const char *p = json;
162+
while ((p = strstr(p, "\"type\":\"array\"")) != NULL) {
163+
/* Find the enclosing '}' for this property object */
164+
const char *end = strchr(p, '}');
165+
ASSERT_NOT_NULL(end);
166+
/* "items" must appear between p and end */
167+
size_t span = (size_t)(end - p);
168+
char *segment = malloc(span + 1);
169+
memcpy(segment, p, span);
170+
segment[span] = '\0';
171+
ASSERT_NOT_NULL(strstr(segment, "\"items\"")); /* array missing items */
172+
free(segment);
173+
p = end;
174+
}
175+
176+
free(json);
177+
PASS();
178+
}
179+
132180
TEST(mcp_text_result) {
133181
char *json = cbm_mcp_text_result("{\"total\":5}", false);
134182
ASSERT_NOT_NULL(json);
@@ -1185,6 +1233,7 @@ SUITE(mcp) {
11851233
/* MCP protocol helpers */
11861234
RUN_TEST(mcp_initialize_response);
11871235
RUN_TEST(mcp_tools_list);
1236+
RUN_TEST(mcp_tools_array_schemas_have_items);
11881237
RUN_TEST(mcp_text_result);
11891238
RUN_TEST(mcp_text_result_error);
11901239

0 commit comments

Comments
 (0)