feat: implement MCP proxy for OpenClaw tool access in contractor agent

Complete the MCP tool call chain:
- contractor-agent bridge exposes /mcp/execute endpoint for tool callbacks
- openclaw-mcp-server.mjs proxies OpenClaw tool defs to Claude as MCP tools
- sdk-adapter passes --mcp-config on first turn with all OpenClaw tools
- tool-test plugin registers contractor_echo in globalThis tool handler map
- agent-config-writer auto-sets tools.profile=full so OpenClaw sends tool defs
- Fix --mcp-config arg ordering: prompt must come before <configs...> flag

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
h z
2026-04-11 13:05:03 +01:00
parent f7c5875eeb
commit 76a7931f97
24 changed files with 12935 additions and 256 deletions

189
scripts/install.mjs Normal file
View File

@@ -0,0 +1,189 @@
#!/usr/bin/env node
/**
* Install / uninstall the contractor-agent plugin into OpenClaw.
*
* Usage:
* node scripts/install.mjs --install
* node scripts/install.mjs --uninstall
*/
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
import { execSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const PROJECT_ROOT = path.resolve(__dirname, "..");
const PLUGIN_ID = "contractor-agent";
const PLUGIN_INSTALL_DIR = path.join(os.homedir(), ".openclaw", "plugins", PLUGIN_ID);
const OPENCLAW_CONFIG = path.join(os.homedir(), ".openclaw", "openclaw.json");
const BRIDGE_PORT = 18800;
const BRIDGE_API_KEY = "contractor-bridge-local";
// ── Helpers ───────────────────────────────────────────────────────────────────
function readConfig() {
return JSON.parse(fs.readFileSync(OPENCLAW_CONFIG, "utf8"));
}
function writeConfig(cfg) {
fs.writeFileSync(OPENCLAW_CONFIG, JSON.stringify(cfg, null, 2) + "\n", "utf8");
}
function setIfMissing(obj, key, value) {
if (obj[key] === undefined || obj[key] === null) obj[key] = value;
}
// ── Install ───────────────────────────────────────────────────────────────────
function install() {
console.log(`[install] Installing ${PLUGIN_ID}...`);
// 1. Copy plugin files to ~/.openclaw/plugins/contractor-agent/
if (fs.existsSync(PLUGIN_INSTALL_DIR)) {
fs.rmSync(PLUGIN_INSTALL_DIR, { recursive: true });
}
fs.mkdirSync(PLUGIN_INSTALL_DIR, { recursive: true });
// OpenClaw expects the plugin entry (index.ts) at the plugin root,
// matching the convention used by existing plugins like dirigent.
// We flatten src/ to the plugin root and copy supporting files alongside it.
// Flatten src/ → plugin root
const srcDir = path.join(PROJECT_ROOT, "src");
fs.cpSync(srcDir, PLUGIN_INSTALL_DIR, { recursive: true });
// Copy package.json (update main to point to index.ts at root)
const pkg = JSON.parse(fs.readFileSync(path.join(PROJECT_ROOT, "package.json"), "utf8"));
pkg.main = "index.ts";
fs.writeFileSync(
path.join(PLUGIN_INSTALL_DIR, "package.json"),
JSON.stringify(pkg, null, 2) + "\n",
);
// Copy openclaw.plugin.json — set main to root index.ts
const manifest = JSON.parse(
fs.readFileSync(path.join(PROJECT_ROOT, "openclaw.plugin.json"), "utf8"),
);
manifest.main = "index.ts";
fs.writeFileSync(
path.join(PLUGIN_INSTALL_DIR, "openclaw.plugin.json"),
JSON.stringify(manifest, null, 2) + "\n",
);
// 2. Install npm dependencies inside the plugin dir
console.log(`[install] Installing npm dependencies...`);
execSync("npm install --omit=dev --no-audit --no-fund", {
cwd: PLUGIN_INSTALL_DIR,
stdio: "inherit",
});
// 3. Update openclaw.json
const cfg = readConfig();
// Add provider
cfg.models = cfg.models ?? {};
cfg.models.providers = cfg.models.providers ?? {};
cfg.models.providers[PLUGIN_ID] = {
baseUrl: `http://127.0.0.1:${BRIDGE_PORT}/v1`,
apiKey: BRIDGE_API_KEY,
api: "openai-completions",
models: [
{
id: "contractor-claude-bridge",
name: "Contractor Claude Bridge",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200000,
maxTokens: 16000,
},
],
};
// Add to plugin allow list
cfg.plugins = cfg.plugins ?? {};
cfg.plugins.allow = cfg.plugins.allow ?? [];
if (!cfg.plugins.allow.includes(PLUGIN_ID)) {
cfg.plugins.allow.push(PLUGIN_ID);
}
// Add load path
cfg.plugins.load = cfg.plugins.load ?? {};
cfg.plugins.load.paths = cfg.plugins.load.paths ?? [];
if (!cfg.plugins.load.paths.includes(PLUGIN_INSTALL_DIR)) {
cfg.plugins.load.paths.push(PLUGIN_INSTALL_DIR);
}
// Add plugin entry
cfg.plugins.entries = cfg.plugins.entries ?? {};
cfg.plugins.entries[PLUGIN_ID] = cfg.plugins.entries[PLUGIN_ID] ?? {};
cfg.plugins.entries[PLUGIN_ID].enabled = true;
// Set default config — setIfMissing so user values are preserved
const pluginCfg = cfg.plugins.entries[PLUGIN_ID].config ?? {};
setIfMissing(pluginCfg, "bridgePort", BRIDGE_PORT);
setIfMissing(pluginCfg, "bridgeApiKey", BRIDGE_API_KEY);
setIfMissing(pluginCfg, "permissionMode", "bypassPermissions");
cfg.plugins.entries[PLUGIN_ID].config = pluginCfg;
writeConfig(cfg);
console.log(`[install] Done. Restart the gateway to activate:`);
console.log(` openclaw gateway restart`);
console.log(` openclaw contractor-agents add --agent-id <id> --workspace <path> --contractor claude`);
}
// ── Uninstall ─────────────────────────────────────────────────────────────────
function uninstall() {
console.log(`[uninstall] Removing ${PLUGIN_ID}...`);
const cfg = readConfig();
// Remove provider
if (cfg.models?.providers?.[PLUGIN_ID]) {
delete cfg.models.providers[PLUGIN_ID];
}
// Remove from allow list
if (Array.isArray(cfg.plugins?.allow)) {
cfg.plugins.allow = cfg.plugins.allow.filter((id) => id !== PLUGIN_ID);
}
// Remove load path
if (Array.isArray(cfg.plugins?.load?.paths)) {
cfg.plugins.load.paths = cfg.plugins.load.paths.filter(
(p) => p !== PLUGIN_INSTALL_DIR,
);
}
// Remove plugin entry
if (cfg.plugins?.entries?.[PLUGIN_ID]) {
delete cfg.plugins.entries[PLUGIN_ID];
}
writeConfig(cfg);
// Remove installed files
if (fs.existsSync(PLUGIN_INSTALL_DIR)) {
fs.rmSync(PLUGIN_INSTALL_DIR, { recursive: true });
console.log(`[uninstall] Removed ${PLUGIN_INSTALL_DIR}`);
}
console.log(`[uninstall] Done. Restart the gateway:`);
console.log(` openclaw gateway restart`);
}
// ── Main ──────────────────────────────────────────────────────────────────────
const arg = process.argv[2];
if (arg === "--install") {
install();
} else if (arg === "--uninstall") {
uninstall();
} else {
console.error("Usage: node scripts/install.mjs --install | --uninstall");
process.exit(1);
}