268 lines
6.0 KiB
JavaScript
Executable File
268 lines
6.0 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/**
|
|
* Zed MCP bridge — translates JSON-RPC 2.0 (Zed's MCP client) to Vibn's
|
|
* simpler {tool, params} HTTP API at /api/mcp.
|
|
*
|
|
* Usage:
|
|
* node mcp-zed-bridge.js
|
|
*
|
|
* Env:
|
|
* VIBN_MCP_URL — defaults to https://vibnai.com/api/mcp
|
|
* VIBN_API_KEY — your vibn_sk_... token
|
|
*/
|
|
|
|
const VIBNDEV_MCP_URL =
|
|
process.env.VIBN_MCP_URL || "https://vibnai.com/api/mcp";
|
|
const VIBNDEV_API_KEY = process.env.VIBN_API_KEY || "";
|
|
|
|
if (!VIBNDEV_API_KEY) {
|
|
process.stderr.write("VIBN_API_KEY is required\n");
|
|
process.exit(1);
|
|
}
|
|
|
|
// ── JSON-RPC helpers ─────────────────────────────────────────────────
|
|
|
|
function sendJson(obj) {
|
|
process.stdout.write(JSON.stringify(obj) + "\n");
|
|
}
|
|
|
|
function log(msg) {
|
|
process.stderr.write(`[vibn-bridge] ${msg}\n`);
|
|
}
|
|
|
|
// ── Tool list from the API capability descriptor ──────────────────────
|
|
|
|
const TOOLS = [
|
|
"workspace.describe",
|
|
"gitea.credentials",
|
|
"projects.list",
|
|
"projects.get",
|
|
"project.recent_errors",
|
|
"project.error_detail",
|
|
"project.error_resolve",
|
|
"apps.list",
|
|
"apps.get",
|
|
"apps.create",
|
|
"apps.update",
|
|
"apps.rewire_git",
|
|
"apps.delete",
|
|
"apps.deploy",
|
|
"apps.deployments",
|
|
"apps.domains.list",
|
|
"apps.domains.set",
|
|
"apps.logs",
|
|
"apps.exec",
|
|
"apps.volumes.list",
|
|
"apps.volumes.wipe",
|
|
"apps.containers.up",
|
|
"apps.containers.ps",
|
|
"apps.repair",
|
|
"apps.templates.list",
|
|
"apps.templates.search",
|
|
"apps.envs.list",
|
|
"apps.envs.upsert",
|
|
"apps.envs.delete",
|
|
"databases.list",
|
|
"databases.create",
|
|
"databases.get",
|
|
"databases.update",
|
|
"databases.delete",
|
|
"auth.list",
|
|
"auth.create",
|
|
"auth.delete",
|
|
"domains.search",
|
|
"domains.list",
|
|
"domains.get",
|
|
"domains.register",
|
|
"domains.attach",
|
|
"storage.describe",
|
|
"storage.provision",
|
|
"storage.inject_env",
|
|
"gitea.repos.list",
|
|
"gitea.repo.get",
|
|
"gitea.repo.create",
|
|
"gitea.file.read",
|
|
"gitea.file.write",
|
|
"gitea.file.delete",
|
|
"gitea.branches.list",
|
|
"gitea.branch.create",
|
|
"devcontainer.ensure",
|
|
"devcontainer.status",
|
|
"devcontainer.suspend",
|
|
"shell.exec",
|
|
"fs.read",
|
|
"fs.write",
|
|
"fs.edit",
|
|
"fs.list",
|
|
"fs.tree",
|
|
"fs.delete",
|
|
"fs.glob",
|
|
"fs.grep",
|
|
"dev_server.start",
|
|
"browser.console",
|
|
"browser.navigate",
|
|
"dev_server.stop",
|
|
"dev_server.list",
|
|
"dev_server.logs",
|
|
"ship",
|
|
].map((name) => ({
|
|
name,
|
|
description: `Vibn workspace tool: ${name}`,
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
projectId: { type: "string", description: "Vibn project ID" },
|
|
},
|
|
},
|
|
}));
|
|
|
|
// ── MCP protocol handlers ────────────────────────────────────────────
|
|
|
|
async function handleInitialize(id) {
|
|
sendJson({
|
|
jsonrpc: "2.0",
|
|
id,
|
|
result: {
|
|
protocolVersion: "2024-11-05",
|
|
capabilities: { tools: {} },
|
|
serverInfo: { name: "vibn-mcp-bridge", version: "1.0.0" },
|
|
},
|
|
});
|
|
}
|
|
|
|
async function handleToolsList(id) {
|
|
sendJson({
|
|
jsonrpc: "2.0",
|
|
id,
|
|
result: { tools: TOOLS },
|
|
});
|
|
}
|
|
|
|
async function handleToolsCall(id, params) {
|
|
const { name, arguments: args } = params;
|
|
|
|
try {
|
|
const res = await fetch(VIBNDEV_MCP_URL, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${VIBNDEV_API_KEY}`,
|
|
},
|
|
body: JSON.stringify({ tool: name, params: args }),
|
|
});
|
|
|
|
const data = await res.json();
|
|
|
|
if (!res.ok) {
|
|
sendJson({
|
|
jsonrpc: "2.0",
|
|
id,
|
|
result: {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: JSON.stringify(data, null, 2),
|
|
},
|
|
],
|
|
isError: true,
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
|
|
sendJson({
|
|
jsonrpc: "2.0",
|
|
id,
|
|
result: {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: JSON.stringify(data, null, 2),
|
|
},
|
|
],
|
|
},
|
|
});
|
|
} catch (err) {
|
|
sendJson({
|
|
jsonrpc: "2.0",
|
|
id,
|
|
result: {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: `Error: ${err.message}`,
|
|
},
|
|
],
|
|
isError: true,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
// ── Main loop ─────────────────────────────────────────────────────────
|
|
|
|
let buffer = "";
|
|
|
|
process.stdin.setEncoding("utf8");
|
|
process.stdin.on("data", (chunk) => {
|
|
buffer += chunk;
|
|
const lines = buffer.split("\n");
|
|
buffer = lines.pop() || "";
|
|
|
|
for (const line of lines) {
|
|
if (!line.trim()) continue;
|
|
let msg;
|
|
try {
|
|
msg = JSON.parse(line);
|
|
} catch {
|
|
continue;
|
|
}
|
|
|
|
if (!msg.jsonrpc || !msg.method) continue;
|
|
const id = msg.id;
|
|
|
|
// Wrap in async IIFE so unhandled rejections don't crash the bridge
|
|
(async () => {
|
|
try {
|
|
switch (msg.method) {
|
|
case "initialize":
|
|
await handleInitialize(id);
|
|
break;
|
|
case "notifications/initialized":
|
|
// No response needed
|
|
break;
|
|
case "tools/list":
|
|
await handleToolsList(id);
|
|
break;
|
|
case "tools/call":
|
|
await handleToolsCall(id, msg.params);
|
|
break;
|
|
default:
|
|
sendJson({
|
|
jsonrpc: "2.0",
|
|
id,
|
|
error: {
|
|
code: -32601,
|
|
message: `Method not found: ${msg.method}`,
|
|
},
|
|
});
|
|
}
|
|
} catch (err) {
|
|
log(`Error handling ${msg.method}: ${err.message}`);
|
|
sendJson({
|
|
jsonrpc: "2.0",
|
|
id,
|
|
error: { code: -32603, message: `Internal error: ${err.message}` },
|
|
});
|
|
}
|
|
})();
|
|
}
|
|
});
|
|
|
|
process.stdin.on("end", () => {
|
|
log("stdin closed, exiting");
|
|
process.exit(0);
|
|
});
|
|
|
|
log(`bridge started, proxying to ${VIBNDEV_MCP_URL}`);
|