UNPKG

@sellable/install

Version:

One-command installer for Sellable MCP in Claude Code, Codex, and Hermes

1,292 lines (1,252 loc) • 37.5 kB
import { spawn } from "node:child_process"; import { createHash, createPublicKey, randomUUID, verify } from "node:crypto"; import { chmodSync, closeSync, constants, existsSync, fsyncSync, lstatSync, openSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync, } from "node:fs"; import { basename, dirname, isAbsolute, join, relative } from "node:path"; import { createInterface } from "node:readline"; import { fileURLToPath } from "node:url"; import { FIXED_TURN_BINDING_KEYS, validateMcpRuntimeBinding, } from "./mcp-runtime-binding.mjs"; const SLACK_BODY_KEYS = Object.freeze([ "schemaVersion", "audience", "workspaceId", "agentId", "runtimeId", "profileId", "revisionId", "installationId", "teamId", "channelId", "threadId", "requesterId", "eventId", "providerRequestId", "slackGeneration", "routeGeneration", "membershipGeneration", "serviceCredentialGeneration", "modelCredentialGeneration", "runtimeGeneration", "fence", "turnId", "bodyDigest", "nonce", "issuedAt", "expiresAt", ]); const SLACK_EXPECTED_KEYS = Object.freeze( SLACK_BODY_KEYS.filter( (key) => !["schemaVersion", "audience", "nonce", "issuedAt", "expiresAt"].includes( key ) ) ); const ENDPOINT_TOOL_KEYS = Object.freeze([ "name", "version", "decision", "generation", ]); const ENDPOINT_BODY_KEYS = Object.freeze([ "schemaVersion", "audience", "principalKind", "routes", "workspaceId", "agentId", "endpointId", "revisionId", "conversationId", "runId", "attemptId", "claimId", "runtimeId", "profileId", "runtimeGeneration", "profileGeneration", "serviceCredentialGeneration", "toolPolicyGeneration", "allowedTools", "allowedToolNames", "allowedToolDigest", "fence", "turnId", "bodyDigest", "nonce", "issuedAt", "expiresAt", ]); const ENDPOINT_EXPECTED_KEYS = Object.freeze( ENDPOINT_BODY_KEYS.filter( (key) => ![ "schemaVersion", "audience", "principalKind", "routes", "nonce", "issuedAt", "expiresAt", ].includes(key) ) ); const DESCRIPTOR_SIGNING_DOMAIN = "sellable-agent/mcp-turn-descriptor/v1"; const SAFE_ID = /^[A-Za-z0-9_.:-]{1,256}$/; const SHA256 = /^[a-f0-9]{64}$/; const MAX_JSONRPC_LINE_BYTES = 1024 * 1024; function stableJson(value) { if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`; if (value && typeof value === "object") { return `{${Object.keys(value) .sort() .map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`) .join(",")}}`; } return JSON.stringify(value); } function exactKeys(value, keys) { return Boolean( value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).sort().join("\0") === [...keys].sort().join("\0") ); } function sha256(value) { return createHash("sha256").update(value).digest("hex"); } function privateDirectory(pathValue) { try { if (!isAbsolute(pathValue) || !existsSync(pathValue)) return null; const link = lstatSync(pathValue); if ( link.isSymbolicLink() || !link.isDirectory() || (link.mode & 0o077) !== 0 ) return null; return realpathSync(pathValue); } catch { return null; } } function directChild(root, candidate) { try { if (!isAbsolute(candidate) || realpathSync(dirname(candidate)) !== root) return false; const canonicalCandidate = join(root, basename(candidate)); const path = relative(root, canonicalCandidate); return ( path.length > 0 && !path.startsWith("..") && basename(candidate) === path ); } catch { return false; } } function descriptorShape(body) { if (!body || typeof body !== "object" || Array.isArray(body)) return false; if (body.schemaVersion === "sellable-agent-mcp-endpoint-run/v1") { if ( !exactKeys(body, ENDPOINT_BODY_KEYS) || body.audience !== "sellable-agent-mcp-context-proxy/v1" || body.principalKind !== "endpoint_run" || stableJson(body.routes) !== stableJson(["sellable-mcp"]) || !SHA256.test(body.bodyDigest ?? "") || !SHA256.test(body.allowedToolDigest ?? "") || !/^[1-9][0-9]{0,19}$/.test(body.fence ?? "") || !SAFE_ID.test(body.nonce ?? "") || !SAFE_ID.test(body.turnId ?? "") ) return false; for (const key of [ "runtimeGeneration", "profileGeneration", "serviceCredentialGeneration", "toolPolicyGeneration", ]) { if (!Number.isSafeInteger(body[key]) || body[key] < 1) return false; } for (const key of [ "workspaceId", "agentId", "endpointId", "revisionId", "conversationId", "runId", "attemptId", "claimId", "runtimeId", "profileId", ]) { if (typeof body[key] !== "string" || !SAFE_ID.test(body[key])) return false; } if ( !Array.isArray(body.allowedTools) || !Array.isArray(body.allowedToolNames) || body.allowedTools.length > 1024 || body.allowedTools.length !== body.allowedToolNames.length || body.allowedTools.some( (tool) => !exactKeys(tool, ENDPOINT_TOOL_KEYS) || typeof tool.name !== "string" || !/^[a-z][a-z0-9_]{0,127}$/.test(tool.name) || typeof tool.version !== "string" || !SAFE_ID.test(tool.version) || !["AUTO", "REQUESTER_CONFIRM", "WORKSPACE_APPROVER", "DENY"].includes( tool.decision ) || !Number.isSafeInteger(tool.generation) || tool.generation < 1 ) || new Set(body.allowedToolNames).size !== body.allowedToolNames.length || stableJson(body.allowedTools.map((tool) => tool.name)) !== stableJson(body.allowedToolNames) || sha256(stableJson(body.allowedTools)) !== body.allowedToolDigest ) return false; const issuedAt = Date.parse(body.issuedAt); const expiresAt = Date.parse(body.expiresAt); return ( Number.isFinite(issuedAt) && Number.isFinite(expiresAt) && expiresAt > issuedAt ); } if (!exactKeys(body, SLACK_BODY_KEYS)) return false; if ( body.schemaVersion !== "sellable-agent-mcp-turn/v1" || body.audience !== "sellable-agent-mcp-context-proxy/v1" || !SHA256.test(body.bodyDigest ?? "") || !/^[1-9][0-9]{0,19}$/.test(body.fence ?? "") || !SAFE_ID.test(body.nonce ?? "") || !SAFE_ID.test(body.turnId ?? "") ) return false; for (const key of [ "slackGeneration", "routeGeneration", "membershipGeneration", "serviceCredentialGeneration", "modelCredentialGeneration", "runtimeGeneration", ]) { if (!Number.isSafeInteger(body[key]) || body[key] < 1) return false; } for (const key of SLACK_EXPECTED_KEYS.filter( (key) => ![ "slackGeneration", "routeGeneration", "membershipGeneration", "serviceCredentialGeneration", "modelCredentialGeneration", "runtimeGeneration", "bodyDigest", "fence", ].includes(key) )) { if ( typeof body[key] !== "string" || body[key].length < 1 || body[key].length > 512 ) return false; } const issuedAt = Date.parse(body.issuedAt); const expiresAt = Date.parse(body.expiresAt); return ( Number.isFinite(issuedAt) && Number.isFinite(expiresAt) && expiresAt > issuedAt ); } function trustedKey(publicKeyPem, fingerprint) { if (typeof publicKeyPem !== "string" || !SHA256.test(fingerprint ?? "")) return null; if (sha256(publicKeyPem) !== fingerprint) return null; try { const key = createPublicKey(publicKeyPem); return key.asymmetricKeyType === "ed25519" ? key : null; } catch { return null; } } function readProtectedJson(pathValue) { if (!isAbsolute(pathValue) || !existsSync(pathValue)) return null; try { const entry = lstatSync(pathValue); if ( entry.isSymbolicLink() || !entry.isFile() || (entry.mode & 0o022) !== 0 || entry.size < 2 || entry.size > 64 * 1024 ) return null; return JSON.parse(readFileSync(pathValue, "utf8")); } catch { return null; } } function descriptorCandidate(descriptorPath, descriptorRoot) { const root = privateDirectory(descriptorRoot); if (!root || !directChild(root, descriptorPath)) return null; try { const entry = lstatSync(descriptorPath); if ( entry.isSymbolicLink() || !entry.isFile() || (entry.mode & 0o077) !== 0 || entry.size < 2 || entry.size > 64 * 1024 ) return null; return JSON.parse(readFileSync(descriptorPath, "utf8")); } catch { return null; } } export function deriveExpectedMcpTurnBinding({ descriptorPath, descriptorRoot, runtimeBindingPath, }) { const binding = readProtectedJson(runtimeBindingPath); const descriptor = descriptorCandidate(descriptorPath, descriptorRoot); if ( !validateMcpRuntimeBinding(binding) || !descriptor || !descriptorShape(descriptor.body) ) return { ok: false, code: "runtime_binding_rejected" }; if (descriptor.body.schemaVersion === "sellable-agent-mcp-endpoint-run/v1") { for (const key of [ "workspaceId", "agentId", "runtimeId", "profileId", "runtimeGeneration", "serviceCredentialGeneration", ]) { if (stableJson(descriptor.body[key]) !== stableJson(binding[key])) { return { ok: false, code: "runtime_binding_mismatch" }; } } return { ok: true, expected: Object.fromEntries( ENDPOINT_EXPECTED_KEYS.map((key) => [key, descriptor.body[key]]) ), }; } for (const key of FIXED_TURN_BINDING_KEYS) { if (stableJson(descriptor.body[key]) !== stableJson(binding[key])) { return { ok: false, code: "runtime_binding_mismatch" }; } } if ( !binding.selectedChannelIds.includes(descriptor.body.channelId) || !binding.memberAllowlist.includes(descriptor.body.requesterId) ) return { ok: false, code: "runtime_binding_mismatch" }; return { ok: true, expected: Object.fromEntries( SLACK_EXPECTED_KEYS.map((key) => [key, descriptor.body[key]]) ), }; } function validateDescriptor({ descriptor, trustRootPublicKeyPem, trustRootFingerprint, expected, now, }) { if (!exactKeys(descriptor, ["body", "signerGeneration", "signature"])) { return { ok: false, code: "descriptor_schema_invalid" }; } if ( !descriptorShape(descriptor.body) || !Number.isSafeInteger(descriptor.signerGeneration) || descriptor.signerGeneration < 1 || typeof descriptor.signature !== "string" || descriptor.signature.length < 32 ) return { ok: false, code: "descriptor_schema_invalid" }; const key = trustedKey(trustRootPublicKeyPem, trustRootFingerprint); if (!key) return { ok: false, code: "descriptor_trust_rejected" }; let verified = false; try { verified = verify( null, Buffer.from( `${DESCRIPTOR_SIGNING_DOMAIN}\0${stableJson({ body: descriptor.body, signerGeneration: descriptor.signerGeneration, })}`, "utf8" ), key, Buffer.from(descriptor.signature, "base64url") ); } catch {} if (!verified) return { ok: false, code: "descriptor_signature_invalid" }; const nowMs = ( now instanceof Date ? now : new Date(now ?? Date.now()) ).getTime(); const issuedAt = Date.parse(descriptor.body.issuedAt); const expiresAt = Date.parse(descriptor.body.expiresAt); if ( !Number.isFinite(nowMs) || nowMs < issuedAt - 5_000 || nowMs >= expiresAt ) { return { ok: false, code: "descriptor_expired" }; } const expectedKeys = descriptor.body.schemaVersion === "sellable-agent-mcp-endpoint-run/v1" ? ENDPOINT_EXPECTED_KEYS : SLACK_EXPECTED_KEYS; if (!exactKeys(expected, expectedKeys)) { return { ok: false, code: "descriptor_binding_mismatch" }; } for (const keyName of expectedKeys) { if ( stableJson(descriptor.body[keyName]) !== stableJson(expected[keyName]) ) { return { ok: false, code: "descriptor_binding_mismatch" }; } } return { ok: true, body: descriptor.body }; } export function installMcpTurnDescriptor({ descriptorPath, descriptorRoot, descriptor, trustRootPublicKeyPem, trustRootFingerprint, expected, now, }) { const root = privateDirectory(descriptorRoot); if (!root || !directChild(root, descriptorPath)) { return { ok: false, code: "descriptor_path_rejected" }; } try { if (existsSync(descriptorPath)) return { ok: false, code: "descriptor_path_rejected" }; const valid = validateDescriptor({ descriptor, trustRootPublicKeyPem, trustRootFingerprint, expected, now, }); if (!valid.ok) return valid; const fd = openSync( descriptorPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | (constants.O_NOFOLLOW ?? 0), 0o600 ); try { writeFileSync(fd, `${JSON.stringify(descriptor)}\n`, "utf8"); } finally { closeSync(fd); } chmodSync(descriptorPath, 0o600); return { ok: true }; } catch { return { ok: false, code: "descriptor_path_rejected" }; } } /** * Runtime refresh seam: validate first, then replace the direct-child private * descriptor atomically. A proxy racing the rename observes either the old * complete descriptor or the new complete descriptor, never partial bytes. */ export function replaceMcpTurnDescriptorAtomically({ descriptorPath, descriptorRoot, descriptor, trustRootPublicKeyPem, trustRootFingerprint, expected, now, }) { const root = privateDirectory(descriptorRoot); if (!root || !directChild(root, descriptorPath)) { return { ok: false, code: "descriptor_path_rejected" }; } const valid = validateDescriptor({ descriptor, trustRootPublicKeyPem, trustRootFingerprint, expected, now, }); if (!valid.ok) return valid; try { if (existsSync(descriptorPath)) { const existing = lstatSync(descriptorPath); if ( existing.isSymbolicLink() || !existing.isFile() || (existing.mode & 0o077) !== 0 ) { return { ok: false, code: "descriptor_path_rejected" }; } } const temporaryPath = join( root, `.${basename(descriptorPath)}.${process.pid}.${randomUUID()}.tmp` ); let descriptorFd; try { descriptorFd = openSync( temporaryPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | (constants.O_NOFOLLOW ?? 0), 0o600 ); writeFileSync(descriptorFd, `${JSON.stringify(descriptor)}\n`, "utf8"); fsyncSync(descriptorFd); closeSync(descriptorFd); descriptorFd = undefined; chmodSync(temporaryPath, 0o600); renameSync(temporaryPath, descriptorPath); const rootFd = openSync(root, constants.O_RDONLY); try { fsyncSync(rootFd); } finally { closeSync(rootFd); } return { ok: true }; } finally { if (descriptorFd !== undefined) closeSync(descriptorFd); rmSync(temporaryPath, { force: true }); } } catch { return { ok: false, code: "descriptor_path_rejected" }; } } function claimDescriptor(descriptorPath, root) { if (!directChild(root, descriptorPath)) return null; const claimedPath = join( root, `.${basename(descriptorPath)}.${process.pid}.${randomUUID()}.claimed` ); try { const link = lstatSync(descriptorPath); if (link.isSymbolicLink() || !link.isFile() || (link.mode & 0o077) !== 0) return null; renameSync(descriptorPath, claimedPath); return claimedPath; } catch { return null; } } function downstreamMetadata(body) { if (body.schemaVersion === "sellable-agent-mcp-endpoint-run/v1") { return { kind: "endpoint_run", workspaceId: body.workspaceId, agentId: body.agentId, endpointId: body.endpointId, revisionId: body.revisionId, conversationId: body.conversationId, runId: body.runId, attemptId: body.attemptId, claimId: body.claimId, runtimeId: body.runtimeId, profileId: body.profileId, runtimeGeneration: body.runtimeGeneration, profileGeneration: body.profileGeneration, serviceCredentialGeneration: body.serviceCredentialGeneration, toolPolicyGeneration: body.toolPolicyGeneration, allowedTools: body.allowedTools, allowedToolNames: body.allowedToolNames, allowedToolDigest: body.allowedToolDigest, fence: body.fence, turnId: body.turnId, bodyDigest: body.bodyDigest, }; } return { channelId: body.channelId, requesterId: body.requesterId, providerRequestId: body.providerRequestId, }; } function burnNonce(consumedRoot, nonce) { const root = privateDirectory(consumedRoot); if (!root) return false; const marker = join(root, `${sha256(nonce)}.consumed`); let fd; try { fd = openSync( marker, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | (constants.O_NOFOLLOW ?? 0), 0o600 ); writeFileSync(fd, `${new Date().toISOString()}\n`, "utf8"); return true; } catch { return false; } finally { if (fd !== undefined) closeSync(fd); } } const endpointSessions = new WeakMap(); export function createMcpEndpointSession() { const handle = Object.freeze({ kind: "sellable-mcp-endpoint-session/v1" }); endpointSessions.set(handle, { activated: false, body: null }); return handle; } export function mcpEndpointSessionActivated(session) { return endpointSessions.get(session)?.activated === true; } function endpointSessionBody(session) { return endpointSessions.get(session)?.body ?? null; } function endpointSessionBodyMatches(left, right) { return ENDPOINT_EXPECTED_KEYS.every( (key) => stableJson(left[key]) === stableJson(right[key]) ); } function activeEndpointSessionBody(session, now) { const state = endpointSessions.get(session); if (!state?.body) return { ok: false, code: "descriptor_missing" }; const nowMs = ( now instanceof Date ? now : new Date(now ?? Date.now()) ).getTime(); if (!Number.isFinite(nowMs) || nowMs >= Date.parse(state.body.expiresAt)) { return { ok: false, code: "descriptor_expired" }; } return { ok: true, body: state.body }; } function activateOrRefreshEndpointSession({ session, descriptorPath, descriptorRoot, consumedRoot, trustRootPublicKeyPem, trustRootFingerprint, expected, now, }) { const state = endpointSessions.get(session); if (!state) return { ok: false, code: "endpoint_session_rejected" }; const root = privateDirectory(descriptorRoot); if (!root || !directChild(root, descriptorPath)) { return { ok: false, code: "descriptor_path_rejected" }; } if (!existsSync(descriptorPath)) return activeEndpointSessionBody(session, now); const claimedPath = claimDescriptor(descriptorPath, root); if (!claimedPath) return { ok: false, code: "descriptor_claim_failed" }; try { let descriptor; try { descriptor = JSON.parse(readFileSync(claimedPath, "utf8")); } catch { return { ok: false, code: "descriptor_schema_invalid" }; } if (!expected) return { ok: false, code: "descriptor_binding_mismatch" }; const valid = validateDescriptor({ descriptor, trustRootPublicKeyPem, trustRootFingerprint, expected, now, }); if (!valid.ok) return valid; if (valid.body.schemaVersion !== "sellable-agent-mcp-endpoint-run/v1") { if (state.body) return { ok: false, code: "endpoint_session_binding_mismatch" }; return { ok: false, code: "endpoint_session_not_applicable" }; } if (state.body && !endpointSessionBodyMatches(state.body, valid.body)) { // Hermes deliberately reuses one stdio MCP transport across sequential // endpoint runs. Rotate that transport in place after proving the next // server-signed descriptor against the runtime binding. Returning a // synthetic transport-expired error here made Hermes mark the Sellable // MCP unavailable mid-conversation, so the following reviewed Calendar // read never reached the backend. The nonce is burned before the session // pointer moves, preserving the exact same single-use fence as first // activation; a stale or replayed descriptor still fails closed. if (!burnNonce(consumedRoot, valid.body.nonce)) { return { ok: false, code: "descriptor_replayed" }; } state.body = Object.freeze({ ...valid.body }); state.activated = true; return { ok: true, body: state.body }; } if (!burnNonce(consumedRoot, valid.body.nonce)) { return { ok: false, code: "descriptor_replayed" }; } state.body = Object.freeze({ ...valid.body }); state.activated = true; return { ok: true, body: state.body }; } finally { rmSync(claimedPath, { force: true }); } } export async function authorizeMcpToolCall({ descriptorPath, descriptorRoot, consumedRoot, trustRootPublicKeyPem, trustRootFingerprint, expected, request, now, callTool, session = /** @type {any} */ (null), }) { if ( !request || request.method !== "tools/call" || !request.params || typeof request.params !== "object" || Array.isArray(request.params) ) return { ok: false, code: "tool_request_invalid" }; if (session) { const valid = activateOrRefreshEndpointSession({ session, descriptorPath, descriptorRoot, consumedRoot, trustRootPublicKeyPem, trustRootFingerprint, expected, now, }); if (!valid.ok) return valid; const downstream = { ...request, params: { ...request.params, _meta: { sellableAgent: downstreamMetadata(valid.body) }, }, }; return { ok: true, result: await callTool(downstream) }; } const root = privateDirectory(descriptorRoot); if (!root || !directChild(root, descriptorPath)) { return { ok: false, code: "descriptor_path_rejected" }; } const claimedPath = claimDescriptor(descriptorPath, root); if (!claimedPath) return { ok: false, code: "descriptor_missing" }; try { let descriptor; try { descriptor = JSON.parse(readFileSync(claimedPath, "utf8")); } catch { return { ok: false, code: "descriptor_schema_invalid" }; } const valid = validateDescriptor({ descriptor, trustRootPublicKeyPem, trustRootFingerprint, expected, now, }); if (!valid.ok) return valid; if (!burnNonce(consumedRoot, valid.body.nonce)) { return { ok: false, code: "descriptor_replayed" }; } const downstream = { ...request, params: { ...request.params, _meta: { sellableAgent: downstreamMetadata(valid.body), }, }, }; const result = await callTool(downstream); return { ok: true, result }; } finally { rmSync(claimedPath, { force: true }); } } export async function authorizeMcpToolList({ descriptorPath, descriptorRoot, trustRootPublicKeyPem, trustRootFingerprint, expected, request, now, callTool, consumedRoot, session = /** @type {any} */ (null), }) { if (!request || request.method !== "tools/list") { return { ok: false, code: "tool_request_invalid" }; } if (session) { const valid = activateOrRefreshEndpointSession({ session, descriptorPath, descriptorRoot, consumedRoot, trustRootPublicKeyPem, trustRootFingerprint, expected, now, }); if (!valid.ok) return valid; const downstream = { ...request, params: { ...(request.params ?? {}), _meta: { sellableAgent: downstreamMetadata(valid.body) }, }, }; return { ok: true, result: await callTool(downstream) }; } const descriptor = descriptorCandidate(descriptorPath, descriptorRoot); if (!descriptor) return { ok: false, code: "descriptor_missing" }; const valid = validateDescriptor({ descriptor, trustRootPublicKeyPem, trustRootFingerprint, expected, now, }); if (!valid.ok) return valid; if (valid.body.schemaVersion !== "sellable-agent-mcp-endpoint-run/v1") { return { ok: true, result: await callTool(request) }; } const downstream = { ...request, params: { ...(request.params ?? {}), _meta: { sellableAgent: downstreamMetadata(valid.body) }, }, }; return { ok: true, result: await callTool(downstream) }; } function parseDownstreamArgs(value) { if (value === undefined || value === "") return []; try { const parsed = JSON.parse(value); if ( !Array.isArray(parsed) || parsed.length > 32 || parsed.some( (item) => typeof item !== "string" || item.length > 4096 || item.includes("\0") ) ) return null; return parsed; } catch { return null; } } function parseJsonRpcLine(line) { if ( typeof line !== "string" || Buffer.byteLength(line) > MAX_JSONRPC_LINE_BYTES ) { return null; } try { const message = JSON.parse(line); return message && typeof message === "object" && !Array.isArray(message) && message.jsonrpc === "2.0" ? message : null; } catch { return null; } } function isHermesSlackToolCall(request) { if ( request?.method !== "tools/call" || !request.params || typeof request.params !== "object" || Array.isArray(request.params) ) return false; const meta = request.params._meta; if (!exactKeys(meta, ["sellableAgent"])) return false; const actor = meta.sellableAgent; const actorKeys = actor && typeof actor === "object" ? Object.keys(actor) : []; if ( !exactKeys( actor, actorKeys.includes("sessionId") ? ["requesterId", "channelId", "providerRequestId", "sessionId"] : ["requesterId", "channelId", "providerRequestId"] ) ) return false; return Object.values(actor).every( (value) => typeof value === "string" && SAFE_ID.test(value) ); } export function endpointHermesSessionId(body) { if (body?.schemaVersion !== "sellable-agent-mcp-endpoint-run/v1") return null; return `sellable_endpoint_${sha256( `${body.conversationId}:${body.runId}:${body.attemptId}` ).slice(0, 32)}`; } export function endpointMcpDescriptorPath(descriptorPath, sessionId) { if ( !isAbsolute(descriptorPath ?? "") || typeof sessionId !== "string" || !SAFE_ID.test(sessionId) ) { return null; } return join(dirname(descriptorPath), `endpoint-${sha256(sessionId)}.json`); } function requestEndpointHermesSessionId(request) { if (request?.method !== "tools/call" && request?.method !== "tools/list") { return null; } const meta = request?.params?._meta; if (!exactKeys(meta, ["sellableEndpoint"])) return null; const endpoint = meta.sellableEndpoint; if (!exactKeys(endpoint, ["sessionId"])) return null; return typeof endpoint.sessionId === "string" && SAFE_ID.test(endpoint.sessionId) ? endpoint.sessionId : null; } function isExactEndpointHermesSessionToolCall(request, body) { if (!isHermesSlackToolCall(request)) return false; const expectedSessionId = endpointHermesSessionId(body); return Boolean( expectedSessionId && request.params._meta.sellableAgent.sessionId === expectedSessionId ); } function messageIdKey(id) { return id === undefined ? null : stableJson(id); } function writeJsonLine(stream, message) { return new Promise((resolve, reject) => { const bytes = `${JSON.stringify(message)}\n`; const onError = (error) => { stream.off("error", onError); reject(error); }; stream.once("error", onError); const complete = () => { stream.off("error", onError); resolve(); }; if (stream.write(bytes)) complete(); else stream.once("drain", complete); }); } export async function runMcpContextProxyStdio({ input = process.stdin, output = process.stdout, errorOutput = process.stderr, env = process.env, spawnProcess = spawn, } = {}) { const descriptorPath = env.SELLABLE_AGENT_TURN_DESCRIPTOR_PATH; const descriptorRoot = descriptorPath && isAbsolute(descriptorPath) ? dirname(descriptorPath) : null; const consumedRoot = env.SELLABLE_AGENT_TURN_CONSUMED_ROOT; const runtimeBindingPath = env.SELLABLE_AGENT_MCP_RUNTIME_BINDING_PATH; const trustRootPath = env.SELLABLE_AGENT_MCP_TRUST_ROOT_PATH; const trustRootFingerprint = env.SELLABLE_AGENT_MCP_TRUST_ROOT_FINGERPRINT; const downstreamCommand = env.SELLABLE_AGENT_MCP_DOWNSTREAM_COMMAND; const downstreamArgs = parseDownstreamArgs( env.SELLABLE_AGENT_MCP_DOWNSTREAM_ARGS_JSON ); if ( !descriptorRoot || !privateDirectory(descriptorRoot) || !privateDirectory(consumedRoot) || !isAbsolute(runtimeBindingPath ?? "") || !isAbsolute(trustRootPath ?? "") || !isAbsolute(downstreamCommand ?? "") || !SHA256.test(trustRootFingerprint ?? "") || !downstreamArgs ) throw new Error("mcp_proxy_configuration_rejected"); let trustRootPublicKeyPem; try { const entry = lstatSync(trustRootPath); if ( entry.isSymbolicLink() || !entry.isFile() || (entry.mode & 0o022) !== 0 ) { throw new Error("trust_root_rejected"); } trustRootPublicKeyPem = readFileSync(trustRootPath, "utf8"); } catch { throw new Error("mcp_proxy_trust_root_rejected"); } if (!trustedKey(trustRootPublicKeyPem, trustRootFingerprint)) { throw new Error("mcp_proxy_trust_root_rejected"); } const child = spawnProcess(downstreamCommand, downstreamArgs, { stdio: ["pipe", "pipe", "pipe"], env, shell: false, }); const pending = new Map(); const childLines = createInterface({ input: child.stdout }); const inputLines = createInterface({ input }); child.stderr.on("data", () => {}); child.on("exit", (code) => { for (const { reject } of pending.values()) { reject(new Error(`mcp_downstream_exited:${code}`)); } pending.clear(); }); const childReader = (async () => { for await (const line of childLines) { const message = parseJsonRpcLine(line); if (!message) throw new Error("mcp_downstream_protocol_rejected"); const key = messageIdKey(message.id); const waiter = key === null ? null : pending.get(key); if (waiter) { pending.delete(key); waiter.resolve(message); } else { await writeJsonLine(output, message); } } })(); const downstreamCall = async (request) => { const key = messageIdKey(request.id); if (key === null || pending.has(key)) { throw new Error("mcp_request_id_rejected"); } const response = new Promise((resolve, reject) => { pending.set(key, { resolve, reject }); }); try { await writeJsonLine(child.stdin, request); } catch (error) { pending.delete(key); throw error; } return response; }; const legacyEndpointSession = createMcpEndpointSession(); const endpointSessionsByHermesId = new Map(); for await (const line of inputLines) { const request = parseJsonRpcLine(line); if (!request) throw new Error("mcp_upstream_protocol_rejected"); const requestNow = Date.now(); for (const [sessionId, session] of endpointSessionsByHermesId) { const body = endpointSessionBody(session); if (!body || requestNow >= Date.parse(body.expiresAt)) { endpointSessionsByHermesId.delete(sessionId); } } const endpointHermesId = requestEndpointHermesSessionId(request); const requestDescriptorPath = endpointHermesId ? endpointMcpDescriptorPath(descriptorPath, endpointHermesId) : descriptorPath; if (!requestDescriptorPath) { await writeJsonLine(output, { jsonrpc: "2.0", id: request.id ?? null, error: { code: -32001, message: "endpoint_session_rejected", data: { code: "endpoint_session_rejected" }, }, }); continue; } let endpointSession = legacyEndpointSession; if (endpointHermesId) { endpointSession = endpointSessionsByHermesId.get(endpointHermesId); if (!endpointSession) { endpointSession = createMcpEndpointSession(); endpointSessionsByHermesId.set(endpointHermesId, endpointSession); } } const candidate = existsSync(requestDescriptorPath) ? descriptorCandidate(requestDescriptorPath, descriptorRoot) : null; const activeBody = endpointSessionBody(endpointSession); const endpointBody = candidate?.body?.schemaVersion === "sellable-agent-mcp-endpoint-run/v1" ? candidate.body : activeBody?.schemaVersion === "sellable-agent-mcp-endpoint-run/v1" ? activeBody : null; const endpointMode = Boolean( mcpEndpointSessionActivated(endpointSession) || endpointBody ); if ( endpointHermesId && endpointBody && endpointHermesSessionId(endpointBody) !== endpointHermesId ) { await writeJsonLine(output, { jsonrpc: "2.0", id: request.id ?? null, error: { code: -32001, message: "endpoint_session_binding_mismatch", data: { code: "endpoint_session_binding_mismatch" }, }, }); continue; } if (endpointHermesId && !endpointMode) { await writeJsonLine(output, { jsonrpc: "2.0", id: request.id ?? null, error: { code: -32001, message: "descriptor_missing", data: { code: "descriptor_missing" }, }, }); continue; } // The patched Hermes bridge adds request-scoped metadata from task-local // ContextVars; the model controls only `arguments` and cannot author MCP // `_meta`. Genuine Slack calls pass through with their actor identity. // API endpoint calls select their own signed descriptor/session above. if ( isHermesSlackToolCall(request) && !( endpointMode && isExactEndpointHermesSessionToolCall(request, endpointBody) ) ) { await writeJsonLine(child.stdin, request); continue; } if (request.method === "tools/list" && endpointMode) { const expected = existsSync(requestDescriptorPath) ? deriveExpectedMcpTurnBinding({ descriptorPath: requestDescriptorPath, descriptorRoot, runtimeBindingPath, }) : { ok: true, expected: null }; const authorized = expected.ok ? await authorizeMcpToolList({ descriptorPath: requestDescriptorPath, descriptorRoot, consumedRoot, trustRootPublicKeyPem, trustRootFingerprint, expected: expected.expected, request, now: new Date(), callTool: downstreamCall, session: endpointSession, }) : expected; if (authorized.ok) await writeJsonLine(output, authorized.result); else { await writeJsonLine(output, { jsonrpc: "2.0", id: request.id ?? null, error: { code: -32001, message: authorized.code, data: { code: authorized.code }, }, }); } continue; } if (request.method !== "tools/call") { await writeJsonLine(child.stdin, request); continue; } // The existing Slack bridge remains the task-local authority when no // server-minted endpoint descriptor is installed. Installing this proxy // for endpoint runs must not weaken or fabricate that Slack path. if (!existsSync(requestDescriptorPath) && !endpointMode) { await writeJsonLine(child.stdin, request); continue; } const expected = existsSync(requestDescriptorPath) ? deriveExpectedMcpTurnBinding({ descriptorPath: requestDescriptorPath, descriptorRoot, runtimeBindingPath, }) : { ok: true, expected: null }; const authorized = expected.ok ? await authorizeMcpToolCall({ descriptorPath: requestDescriptorPath, descriptorRoot, consumedRoot, trustRootPublicKeyPem, trustRootFingerprint, expected: expected.expected, request, now: new Date(), callTool: downstreamCall, ...(endpointMode ? { session: endpointSession } : {}), }) : expected; if (authorized.ok) { await writeJsonLine(output, authorized.result); } else { await writeJsonLine(output, { jsonrpc: "2.0", id: request.id ?? null, error: { code: -32001, message: authorized.code, data: { code: authorized.code }, }, }); } } child.stdin.end(); await childReader; return { ok: true }; } if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) { runMcpContextProxyStdio().catch(() => { process.stderr.write("sellable-agent MCP context proxy failed closed\n"); process.exitCode = 70; }); }