Skip to content

Commit 63ed678

Browse files
authored
Merge pull request #656 from caioribeiroclw-pixel/fix/openclaw-mcp-config
Fix OpenClaw MCP config path
2 parents 36d8328 + 2358b3c commit 63ed678

4 files changed

Lines changed: 189 additions & 4 deletions

File tree

scripts/smoke-test.sh

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -980,11 +980,16 @@ fi
980980
echo "OK 8v: VS Code MCP"
981981

982982
# 8w: OpenClaw MCP
983-
CMD=$(json_get "$FAKE_HOME/.openclaw/openclaw.json" "d['mcpServers']['codebase-memory-mcp']['command']")
983+
CMD=$(json_get "$FAKE_HOME/.openclaw/openclaw.json" "d['mcp']['servers']['codebase-memory-mcp']['command']")
984984
if ! path_match "$CMD" "$SELF_PATH"; then
985985
echo "FAIL 8w: OpenClaw command='$CMD'"
986986
exit 1
987987
fi
988+
ENABLED=$(json_get "$FAKE_HOME/.openclaw/openclaw.json" "d['mcp']['servers']['codebase-memory-mcp'].get('enabled')")
989+
if [ "$ENABLED" != "True" ]; then
990+
echo "FAIL 8w: OpenClaw enabled='$ENABLED'"
991+
exit 1
992+
fi
988993
echo "OK 8w: OpenClaw MCP"
989994

990995
# 8x: Consolidated skill (old 4-skill dirs cleaned up, replaced by 1)
@@ -1091,7 +1096,7 @@ fi
10911096
if cat "$FAKE_HOME/.openclaw/openclaw.json" 2>/dev/null | python3 -c "
10921097
import json, sys
10931098
d = json.load(sys.stdin)
1094-
sys.exit(1 if 'codebase-memory-mcp' in d.get('mcpServers', {}) else 0)
1099+
sys.exit(1 if 'codebase-memory-mcp' in d.get('mcp', {}).get('servers', {}) else 0)
10951100
" 2>/dev/null; then
10961101
echo "OK 9k: OpenClaw MCP removed"
10971102
else

src/cli/cli.c

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,96 @@ int cbm_remove_editor_mcp(const char *config_path) {
863863
return rc;
864864
}
865865

866+
/* ── OpenClaw MCP (nested mcp.servers with command + args) ────── */
867+
868+
int cbm_install_openclaw_mcp(const char *binary_path, const char *config_path) {
869+
if (!binary_path || !config_path) {
870+
return CLI_ERR;
871+
}
872+
873+
yyjson_mut_doc *mdoc = yyjson_mut_doc_new(NULL);
874+
if (!mdoc) {
875+
return CLI_ERR;
876+
}
877+
878+
yyjson_doc *doc = read_json_file(config_path);
879+
yyjson_mut_val *root;
880+
if (doc) {
881+
root = yyjson_val_mut_copy(mdoc, yyjson_doc_get_root(doc));
882+
yyjson_doc_free(doc);
883+
} else {
884+
root = yyjson_mut_obj(mdoc);
885+
}
886+
if (!root) {
887+
yyjson_mut_doc_free(mdoc);
888+
return CLI_ERR;
889+
}
890+
yyjson_mut_doc_set_root(mdoc, root);
891+
892+
yyjson_mut_val *mcp = yyjson_mut_obj_get(root, "mcp");
893+
if (!mcp || !yyjson_mut_is_obj(mcp)) {
894+
mcp = yyjson_mut_obj(mdoc);
895+
yyjson_mut_obj_add_val(mdoc, root, "mcp", mcp);
896+
}
897+
898+
yyjson_mut_val *servers = yyjson_mut_obj_get(mcp, "servers");
899+
if (!servers || !yyjson_mut_is_obj(servers)) {
900+
servers = yyjson_mut_obj(mdoc);
901+
yyjson_mut_obj_add_val(mdoc, mcp, "servers", servers);
902+
}
903+
904+
yyjson_mut_obj_remove_key(servers, "codebase-memory-mcp");
905+
906+
yyjson_mut_val *entry = yyjson_mut_obj(mdoc);
907+
yyjson_mut_obj_add_bool(mdoc, entry, "enabled", true);
908+
yyjson_mut_obj_add_str(mdoc, entry, "command", binary_path);
909+
yyjson_mut_val *args = yyjson_mut_arr(mdoc);
910+
yyjson_mut_obj_add_val(mdoc, entry, "args", args);
911+
yyjson_mut_obj_add_val(mdoc, servers, "codebase-memory-mcp", entry);
912+
913+
int rc = write_json_file(config_path, mdoc);
914+
yyjson_mut_doc_free(mdoc);
915+
return rc;
916+
}
917+
918+
int cbm_remove_openclaw_mcp(const char *config_path) {
919+
if (!config_path) {
920+
return CLI_ERR;
921+
}
922+
923+
yyjson_doc *doc = read_json_file(config_path);
924+
if (!doc) {
925+
return CLI_ERR;
926+
}
927+
928+
yyjson_mut_doc *mdoc = yyjson_mut_doc_new(NULL);
929+
yyjson_mut_val *root = yyjson_val_mut_copy(mdoc, yyjson_doc_get_root(doc));
930+
yyjson_doc_free(doc);
931+
if (!root) {
932+
yyjson_mut_doc_free(mdoc);
933+
return CLI_ERR;
934+
}
935+
yyjson_mut_doc_set_root(mdoc, root);
936+
937+
yyjson_mut_val *mcp = yyjson_mut_obj_get(root, "mcp");
938+
if (!mcp || !yyjson_mut_is_obj(mcp)) {
939+
yyjson_mut_doc_free(mdoc);
940+
return 0;
941+
}
942+
943+
yyjson_mut_val *servers = yyjson_mut_obj_get(mcp, "servers");
944+
if (!servers || !yyjson_mut_is_obj(servers)) {
945+
yyjson_mut_doc_free(mdoc);
946+
return 0;
947+
}
948+
949+
yyjson_mut_obj_remove_key(servers, "codebase-memory-mcp");
950+
951+
int rc = write_json_file(config_path, mdoc);
952+
yyjson_mut_doc_free(mdoc);
953+
return rc;
954+
}
955+
866956
/* ── VS Code MCP (servers key with type:stdio) ────────────────── */
867957

868958
int cbm_install_vscode_mcp(const char *binary_path, const char *config_path) {
@@ -3293,7 +3383,7 @@ static void install_editor_agent_configs(const cbm_detected_agents_t *agents, co
32933383
char cp[CLI_BUF_1K];
32943384
snprintf(cp, sizeof(cp), "%s/.openclaw/openclaw.json", home);
32953385
install_generic_agent_config("OpenClaw", binary_path, cp, NULL, dry_run,
3296-
cbm_install_editor_mcp);
3386+
cbm_install_openclaw_mcp);
32973387
}
32983388
if (agents->kiro) {
32993389
char cp[CLI_BUF_1K];
@@ -3822,7 +3912,7 @@ static void uninstall_editor_agents(const cbm_detected_agents_t *agents, const c
38223912
char cp[CLI_BUF_1K];
38233913
snprintf(cp, sizeof(cp), "%s/.openclaw/openclaw.json", home);
38243914
uninstall_agent_mcp_instr((mcp_uninstall_args_t){"OpenClaw", cp, NULL}, dry_run,
3825-
cbm_remove_editor_mcp);
3915+
cbm_remove_openclaw_mcp);
38263916
}
38273917
if (agents->kiro) {
38283918
char cp[CLI_BUF_1K];

src/cli/cli.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,16 @@ int cbm_install_editor_mcp(const char *binary_path, const char *config_path);
9494
* Returns 0 on success. */
9595
int cbm_remove_editor_mcp(const char *config_path);
9696

97+
/* Install MCP server entry in OpenClaw JSON config.
98+
* Format: { "mcp": { "servers": { "codebase-memory-mcp":
99+
* { "enabled": true, "command": binary_path, "args": [] } } } }
100+
* Preserves existing entries. Returns 0 on success. */
101+
int cbm_install_openclaw_mcp(const char *binary_path, const char *config_path);
102+
103+
/* Remove MCP server entry from OpenClaw JSON config.
104+
* Returns 0 on success. */
105+
int cbm_remove_openclaw_mcp(const char *config_path);
106+
97107
/* Install MCP server entry in VS Code JSON config.
98108
* Format: { "servers": { "codebase-memory-mcp": { "type": "stdio", "command": binary_path } } }
99109
* Returns 0 on success. */

tests/test_cli.c

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,83 @@ TEST(cli_gemini_mcp_install) {
718718
PASS();
719719
}
720720

721+
TEST(cli_openclaw_mcp_install_uses_nested_servers) {
722+
char tmpdir[256];
723+
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-openclaw-mcp-XXXXXX");
724+
if (!cbm_mkdtemp(tmpdir))
725+
FAIL("cbm_mkdtemp failed");
726+
727+
char configpath[512];
728+
snprintf(configpath, sizeof(configpath), "%s/.openclaw/openclaw.json", tmpdir);
729+
730+
int rc = cbm_install_openclaw_mcp("/usr/local/bin/codebase-memory-mcp", configpath);
731+
ASSERT_EQ(rc, 0);
732+
733+
const char *data = read_test_file(configpath);
734+
ASSERT_NOT_NULL(data);
735+
ASSERT(strstr(data, "\"mcp\"") != NULL);
736+
ASSERT(strstr(data, "\"servers\"") != NULL);
737+
ASSERT(strstr(data, "\"enabled\": true") != NULL);
738+
ASSERT(strstr(data, "\"command\": \"/usr/local/bin/codebase-memory-mcp\"") != NULL);
739+
ASSERT(strstr(data, "\"args\": []") != NULL);
740+
ASSERT(strstr(data, "\"mcpServers\"") == NULL);
741+
742+
test_rmdir_r(tmpdir);
743+
PASS();
744+
}
745+
746+
TEST(cli_openclaw_mcp_preserves_existing_config) {
747+
char tmpdir[256];
748+
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-openclaw-mcp-XXXXXX");
749+
if (!cbm_mkdtemp(tmpdir))
750+
FAIL("cbm_mkdtemp failed");
751+
752+
char dir[512];
753+
snprintf(dir, sizeof(dir), "%s/.openclaw", tmpdir);
754+
test_mkdirp(dir);
755+
756+
char configpath[512];
757+
snprintf(configpath, sizeof(configpath), "%s/openclaw.json", dir);
758+
write_test_file(configpath,
759+
"{\"theme\":\"dark\",\"mcp\":{\"servers\":{\"other\":{\"command\":\"x\"}}}}");
760+
761+
int rc = cbm_install_openclaw_mcp("/usr/local/bin/codebase-memory-mcp", configpath);
762+
ASSERT_EQ(rc, 0);
763+
764+
const char *data = read_test_file(configpath);
765+
ASSERT_NOT_NULL(data);
766+
ASSERT(strstr(data, "theme") != NULL);
767+
ASSERT(strstr(data, "other") != NULL);
768+
ASSERT(strstr(data, "codebase-memory-mcp") != NULL);
769+
ASSERT(strstr(data, "\"mcpServers\"") == NULL);
770+
771+
test_rmdir_r(tmpdir);
772+
PASS();
773+
}
774+
775+
TEST(cli_openclaw_mcp_uninstall_uses_nested_servers) {
776+
char tmpdir[256];
777+
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-openclaw-mcp-XXXXXX");
778+
if (!cbm_mkdtemp(tmpdir))
779+
FAIL("cbm_mkdtemp failed");
780+
781+
char configpath[512];
782+
snprintf(configpath, sizeof(configpath), "%s/.openclaw/openclaw.json", tmpdir);
783+
784+
ASSERT_EQ(cbm_install_openclaw_mcp("/usr/local/bin/codebase-memory-mcp", configpath), 0);
785+
ASSERT_EQ(cbm_remove_openclaw_mcp(configpath), 0);
786+
787+
const char *data = read_test_file(configpath);
788+
ASSERT_NOT_NULL(data);
789+
ASSERT(strstr(data, "\"mcp\"") != NULL);
790+
ASSERT(strstr(data, "\"servers\"") != NULL);
791+
ASSERT(strstr(data, "\"codebase-memory-mcp\"") == NULL);
792+
ASSERT(strstr(data, "\"mcpServers\"") == NULL);
793+
794+
test_rmdir_r(tmpdir);
795+
PASS();
796+
}
797+
721798
/* ═══════════════════════════════════════════════════════════════════
722799
* VS Code MCP config tests
723800
* ═══════════════════════════════════════════════════════════════════ */
@@ -2693,6 +2770,9 @@ SUITE(cli) {
26932770
RUN_TEST(cli_editor_mcp_preserves_others);
26942771
RUN_TEST(cli_editor_mcp_uninstall);
26952772
RUN_TEST(cli_gemini_mcp_install);
2773+
RUN_TEST(cli_openclaw_mcp_install_uses_nested_servers);
2774+
RUN_TEST(cli_openclaw_mcp_preserves_existing_config);
2775+
RUN_TEST(cli_openclaw_mcp_uninstall_uses_nested_servers);
26962776

26972777
/* VS Code MCP (2 tests — install_test.go) */
26982778
RUN_TEST(cli_vscode_mcp_install);

0 commit comments

Comments
 (0)