@sellable/install
Version:
One-command installer for Sellable MCP in Claude Code, Codex, and Hermes
54 lines (50 loc) • 1.95 kB
JavaScript
// Claude Code's only MCP server for a tool-lane run. It starts the profile's
// own `sellable` server (the context proxy, with exactly its configured
// environment) and adds this run's endpoint session to every tools/list and
// tools/call. The proxy then loads the run's signed turn descriptor, lists
// only the run's allowed tools and stamps the run identity on each call; the
// backend checks it again. Nothing in this file grants anything.
import { spawn } from "node:child_process";
import { readFileSync } from "node:fs";
import { createInterface } from "node:readline";
import { fileURLToPath } from "node:url";
export function tagEndpointSession(line, sessionId) {
let message;
try {
message = JSON.parse(line);
} catch {
return line;
}
if (
!message ||
typeof message !== "object" ||
!["tools/list", "tools/call"].includes(message.method)
)
return line;
const params =
message.params && typeof message.params === "object" ? message.params : {};
// The proxy accepts exactly this shape and nothing else in _meta.
return JSON.stringify({
...message,
params: { ...params, _meta: { sellableEndpoint: { sessionId } } },
});
}
function main(specPath) {
const spec = JSON.parse(readFileSync(specPath, "utf8"));
if (!/^sellable_endpoint_[a-f0-9]{32}$/.test(spec.sessionId ?? ""))
throw new Error("claude_runs_mcp_session_rejected");
const child = spawn(spec.command, spec.args, {
stdio: ["pipe", "inherit", "ignore"],
env: spec.env,
});
child.once("exit", (code) => process.exit(code ?? 1));
child.stdin.on("error", () => {});
const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
lines.on("line", (line) =>
child.stdin.write(`${tagEndpointSession(line, spec.sessionId)}\n`)
);
lines.on("close", () => child.stdin.end());
}
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
main(process.argv[2]);
}