run402-mcp
Version:
MCP server for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.
715 lines • 28.7 kB
JavaScript
import { createHash } from "node:crypto";
import { lstat, mkdir, mkdtemp, opendir, readFile, rm, stat, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { LocalError, NetworkError } from "../errors.js";
import { Archives } from "../namespaces/archives.js";
const DEFAULT_CORE_URL = "http://127.0.0.1:4020";
const PROJECT_ARCHIVE_VERSION = "run402-project-archive.v1";
const PROJECT_ARCHIVE_DIGEST_IDENTITY = "run402-project-archive-logical-v1";
const TAR_BLOCK_BYTES = 512;
const ZERO_BLOCK = Buffer.alloc(TAR_BLOCK_BYTES);
const SUPPORTED_CAPABILITIES = new Set([
"run402.core.release-state.v1",
"run402.core.database.phased-postgres-copy.v1",
"run402.core.storage.cas.v1",
"run402.core.functions.node22.v1",
"run402.core.astro-ssr.v1",
"run402.core.auth-stubs.v1",
"run402.core.secret-requirements.v1",
]);
export class NodeArchives extends Archives {
constructor(client) {
super(client);
}
inspect(archivePath) {
return inspectArchive(archivePath);
}
verify(archivePath) {
return verifyArchive(archivePath);
}
importToCore(opts) {
return importArchiveToCore(opts);
}
}
export async function inspectArchive(archivePath) {
return verifyLocalPortableArchive(path.resolve(archivePath));
}
export async function verifyArchive(archivePath) {
return verifyLocalPortableArchive(path.resolve(archivePath));
}
export async function importArchiveToCore(opts) {
const archivePath = path.resolve(opts.archivePath);
const verification = await verifyLocalPortableArchive(archivePath);
if (!verification.ok) {
return failedImportResult({
archiveDigest: verification.archive_digest,
requiredSecrets: verification.required_secrets,
diagnostics: [
diagnostic({
code: "IMPORT_VERIFY_FAILED",
resourceType: "archive",
message: "Portable archive verification failed locally; Core import was not called.",
context: { diagnostic_count: verification.diagnostics.length },
}),
...verification.diagnostics,
],
nextAction: {
type: "run_command",
message: "Run `run402 archives verify <archive> --json`, fix the archive issue, then retry import.",
},
});
}
const secretValues = {
...(opts.envFile ? await readEnvFile(opts.envFile) : {}),
...(opts.secretValues ?? {}),
};
const staged = await stageArchiveForCoreImport(archivePath, verification);
try {
const coreUrl = normalizeCoreUrl(opts.coreUrl);
let res;
try {
res = await fetch(`${coreUrl}/archives/v1/import`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
archive_path: staged.archivePath,
name: opts.name,
dry_run: opts.dryRun ?? false,
require_runnable: opts.requireRunnable ?? false,
secret_values: secretValues,
}),
});
}
catch (err) {
throw new NetworkError(`Network error while importing archive into Core: ${err.message}`, err, "importing archive into Core");
}
const body = await parseJsonBody(res);
if (isImportResult(body))
return body;
if (!res.ok) {
return failedImportResult({
archiveDigest: verification.archive_digest,
requiredSecrets: verification.required_secrets,
diagnostics: [
diagnostic({
code: bodyErrorCode(body),
resourceType: "core_import",
message: bodyErrorMessage(body, `Core import failed with HTTP ${res.status}.`),
retryable: res.status >= 500 || res.status === 429,
context: {
http_status: res.status,
core_url: coreUrl,
body: safeBodyContext(body),
},
}),
],
nextAction: {
type: res.status >= 500 ? "retry_later" : "read_docs",
message: "Fix the reported Core import error and retry.",
},
});
}
return failedImportResult({
archiveDigest: verification.archive_digest,
requiredSecrets: verification.required_secrets,
diagnostics: [
diagnostic({
code: "IMPORT_CONFORMANCE_FAILED",
resourceType: "core_import",
message: "Core import returned an unexpected success body.",
context: { core_url: coreUrl, body: safeBodyContext(body) },
}),
],
nextAction: {
type: "contact_support",
message: "Report the unexpected Core import response with the JSON body.",
},
});
}
finally {
if (staged.cleanupPath) {
await rm(staged.cleanupPath, { recursive: true, force: true }).catch(() => undefined);
}
}
}
export async function readEnvFile(envFile) {
const fullPath = path.resolve(envFile);
const text = await readFile(fullPath, "utf8");
const values = {};
let lineNo = 0;
for (const originalLine of text.split(/\r?\n/)) {
lineNo += 1;
const line = originalLine.trim();
if (!line || line.startsWith("#"))
continue;
const normalized = line.startsWith("export ") ? line.slice("export ".length).trim() : line;
const eq = normalized.indexOf("=");
if (eq <= 0) {
throw new LocalError(`Invalid env file line ${lineNo}: expected KEY=value`, "reading archive env file", {
code: "INVALID_ENV_FILE",
details: { path: fullPath, line: lineNo },
});
}
const key = normalized.slice(0, eq).trim();
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
throw new LocalError(`Invalid env var name on line ${lineNo}: ${key}`, "reading archive env file", {
code: "INVALID_ENV_FILE",
details: { path: fullPath, line: lineNo, key },
});
}
values[key] = parseEnvValue(normalized.slice(eq + 1).trim());
}
return values;
}
async function stageArchiveForCoreImport(archivePath, verification) {
if (verification.transport === "directory") {
return { archivePath, cleanupPath: null };
}
if (verification.transport !== "tar") {
throw new LocalError("Core import supports directory or uncompressed tar archives only.", "staging archive for Core import", {
code: "ARCHIVE_MEDIA_TYPE_UNSUPPORTED",
details: { transport: verification.transport },
});
}
const tmpRoot = await mkdtemp(path.join(tmpdir(), "run402-archive-"));
await extractUncompressedTar(archivePath, tmpRoot);
return { archivePath: tmpRoot, cleanupPath: tmpRoot };
}
async function verifyLocalPortableArchive(archivePath) {
const diagnostics = [];
let archive;
try {
archive = await readArchiveEntries(archivePath);
}
catch (err) {
const d = errorToDiagnostic(err);
return {
ok: false,
archive_version: null,
archive_digest: null,
transport: null,
file_count: 0,
total_bytes: 0,
descriptor_count: 0,
required_capabilities: [],
required_secrets: [],
auth_subject_stub_count: 0,
export_report: null,
portability_report: null,
diagnostics: [d],
};
}
const layout = parseJsonEntry(archive, "run402-layout.json", diagnostics);
const index = parseJsonEntry(archive, "index.json", diagnostics);
let archiveDigest = null;
let requiredSecrets = [];
let authSubjectStubCount = 0;
let exportReport = null;
let portabilityReport = null;
if (layout && layout.archive_version !== PROJECT_ARCHIVE_VERSION) {
diagnostics.push(diagnostic({
code: "ARCHIVE_UNSUPPORTED_VERSION",
resourceType: "layout",
path: "run402-layout.json",
message: "Archive layout version is not supported.",
context: { archive_version: layout.archive_version },
}));
}
if (!index) {
diagnostics.push(diagnostic({
code: "ARCHIVE_DESCRIPTOR_MISSING",
resourceType: "archive",
path: "index.json",
message: "Portable archive is missing index.json.",
}));
}
else {
if (index.archive_version !== PROJECT_ARCHIVE_VERSION) {
diagnostics.push(diagnostic({
code: "ARCHIVE_UNSUPPORTED_VERSION",
resourceType: "archive",
path: "index.json",
message: "Archive version is not supported.",
context: { archive_version: index.archive_version },
}));
}
for (const cap of index.required_capabilities ?? []) {
if (!SUPPORTED_CAPABILITIES.has(cap)) {
diagnostics.push(diagnostic({
code: "ARCHIVE_UNSUPPORTED_REQUIRED_CAPABILITY",
resourceType: "capability",
resourceId: cap,
message: `Archive requires unsupported capability ${cap}.`,
context: { capability: cap },
}));
}
}
verifyDescriptors(index, archive, diagnostics);
archiveDigest = computeLogicalDigest(index);
if (index.archive_digest && index.archive_digest !== archiveDigest) {
diagnostics.push(diagnostic({
code: "ARCHIVE_DIGEST_MISMATCH",
resourceType: "archive",
path: "index.json",
message: "Archive logical digest does not match index.json.",
context: { expected_digest: index.archive_digest, actual_digest: archiveDigest },
}));
}
exportReport = readJsonDescriptor(index, archive, "export_report", diagnostics);
portabilityReport = readJsonDescriptor(index, archive, "portability_report", diagnostics);
const secretRequirements = readJsonDescriptor(index, archive, "secret_requirements", diagnostics);
requiredSecrets = Array.isArray(secretRequirements?.secrets)
? secretRequirements.secrets.filter((s) => typeof s?.name === "string")
: [];
authSubjectStubCount = countAuthSubjectStubs(index, archive);
if (Array.isArray(portabilityReport?.entries)) {
diagnostics.push(...portabilityReport.entries.filter(isArchiveDiagnostic));
}
}
return {
ok: !diagnostics.some((d) => d.severity === "blocking"),
archive_version: index?.archive_version === PROJECT_ARCHIVE_VERSION ? PROJECT_ARCHIVE_VERSION : null,
archive_digest: archiveDigest,
transport: archive.transport,
file_count: archive.entries.size,
total_bytes: archive.totalBytes,
descriptor_count: index?.descriptors ? Object.keys(index.descriptors).length : 0,
required_capabilities: Array.isArray(index?.required_capabilities) ? index.required_capabilities : [],
required_secrets: requiredSecrets,
auth_subject_stub_count: authSubjectStubCount,
export_report: exportReport,
portability_report: portabilityReport,
diagnostics,
};
}
async function readArchiveEntries(archivePath) {
const st = await lstat(archivePath);
if (st.isDirectory()) {
const entries = new Map();
let totalBytes = 0;
async function walk(current, relBase) {
const dir = await opendir(current);
for await (const entry of dir) {
const abs = path.join(current, entry.name);
const rel = path.posix.join(relBase, entry.name);
const childStat = await lstat(abs);
if (childStat.isSymbolicLink()) {
throw new LocalError(`Archive contains unsupported symlink: ${rel}`, "reading archive", {
code: "ARCHIVE_ENTRY_TYPE_UNSUPPORTED",
details: { path: rel },
});
}
if (childStat.isDirectory()) {
await walk(abs, rel);
}
else if (childStat.isFile()) {
const safeRel = sanitizeTarPath(rel);
if (entries.has(safeRel)) {
throw new LocalError(`Duplicate archive path: ${safeRel}`, "reading archive", {
code: "ARCHIVE_DUPLICATE_PATH",
details: { path: safeRel },
});
}
const bytes = await readFile(abs);
entries.set(safeRel, bytes);
totalBytes += bytes.byteLength;
}
else {
throw new LocalError(`Archive contains unsupported entry: ${rel}`, "reading archive", {
code: "ARCHIVE_ENTRY_TYPE_UNSUPPORTED",
details: { path: rel },
});
}
}
}
await walk(archivePath, "");
return { entries, transport: "directory", totalBytes };
}
if (!st.isFile()) {
throw new LocalError("Archive path must be a directory or uncompressed tar file.", "reading archive", {
code: "ARCHIVE_ENTRY_TYPE_UNSUPPORTED",
});
}
return readTarEntries(archivePath);
}
async function readTarEntries(tarPath) {
const bytes = await readFile(tarPath);
const entries = new Map();
let totalBytes = 0;
let offset = 0;
while (offset + TAR_BLOCK_BYTES <= bytes.byteLength) {
const header = bytes.subarray(offset, offset + TAR_BLOCK_BYTES);
offset += TAR_BLOCK_BYTES;
if (header.equals(ZERO_BLOCK))
break;
const name = readTarString(header, 0, 100);
const prefix = readTarString(header, 345, 155);
const type = readTarString(header, 156, 1) || "0";
const size = readTarOctal(header, 124, 12);
const rel = sanitizeTarPath(prefix ? `${prefix}/${name}` : name);
if (entries.has(rel)) {
throw new LocalError(`Duplicate archive path: ${rel}`, "reading archive tar", {
code: "ARCHIVE_DUPLICATE_PATH",
details: { path: rel },
});
}
if (type === "5") {
offset += Math.ceil(size / TAR_BLOCK_BYTES) * TAR_BLOCK_BYTES;
continue;
}
if (type !== "0" && type !== "\0") {
throw new LocalError(`Unsupported tar entry type ${JSON.stringify(type)} at ${rel}`, "reading archive tar", {
code: "ARCHIVE_ENTRY_TYPE_UNSUPPORTED",
details: { path: rel, type },
});
}
const end = offset + size;
if (end > bytes.byteLength) {
throw new LocalError("Malformed tar: entry exceeds file size", "reading archive tar", {
code: "ARCHIVE_MALFORMED_TAR",
details: { path: rel },
});
}
const payload = bytes.subarray(offset, end);
entries.set(rel, payload);
totalBytes += payload.byteLength;
offset += Math.ceil(size / TAR_BLOCK_BYTES) * TAR_BLOCK_BYTES;
}
return { entries, transport: "tar", totalBytes };
}
function parseJsonEntry(archive, relPath, diagnostics) {
const bytes = archive.entries.get(relPath);
if (!bytes) {
diagnostics.push(diagnostic({
code: "ARCHIVE_DESCRIPTOR_MISSING",
resourceType: "descriptor",
path: relPath,
message: `Archive descriptor ${relPath} is missing.`,
}));
return null;
}
try {
return JSON.parse(Buffer.from(bytes).toString("utf8"));
}
catch (err) {
diagnostics.push(diagnostic({
code: "ARCHIVE_MALFORMED_JSON",
resourceType: "descriptor",
path: relPath,
message: `Archive descriptor ${relPath} is not valid JSON.`,
context: { error: err.message },
}));
return null;
}
}
function readJsonDescriptor(index, archive, key, diagnostics) {
const descriptor = index.descriptors?.[key];
const relPath = descriptor?.path ?? fallbackDescriptorPath(key);
if (!relPath || !archive.entries.has(relPath))
return null;
return parseJsonEntry(archive, relPath, diagnostics);
}
function fallbackDescriptorPath(key) {
switch (key) {
case "export_report": return "manifest/export-report.json";
case "portability_report": return "manifest/portability-report.json";
case "secret_requirements": return "secrets/requirements.json";
case "auth_subjects": return "auth/subjects.ndjson";
default: return null;
}
}
function verifyDescriptors(index, archive, diagnostics) {
for (const [name, descriptor] of Object.entries(index.descriptors ?? {})) {
if (!descriptor.path)
continue;
let rel;
try {
rel = sanitizeTarPath(descriptor.path);
}
catch (err) {
diagnostics.push(errorToDiagnostic(err));
continue;
}
const bytes = archive.entries.get(rel);
if (!bytes) {
diagnostics.push(diagnostic({
code: descriptor.mediaType === "application/octet-stream" ? "ARCHIVE_BLOB_MISSING" : "ARCHIVE_DESCRIPTOR_MISSING",
resourceType: "descriptor",
resourceId: name,
path: rel,
message: `Archive descriptor ${name} references missing path ${rel}.`,
}));
continue;
}
if (typeof descriptor.size === "number" && descriptor.size !== bytes.byteLength) {
diagnostics.push(diagnostic({
code: "ARCHIVE_SIZE_MISMATCH",
resourceType: "descriptor",
resourceId: name,
path: rel,
message: `Archive descriptor ${name} size mismatch.`,
context: { expected_size: descriptor.size, actual_size: bytes.byteLength },
}));
}
if (descriptor.digest && descriptor.digest !== digestBytes(bytes)) {
diagnostics.push(diagnostic({
code: "ARCHIVE_DIGEST_MISMATCH",
resourceType: "descriptor",
resourceId: name,
path: rel,
message: `Archive descriptor ${name} digest mismatch.`,
context: { expected_digest: descriptor.digest, actual_digest: digestBytes(bytes) },
}));
}
}
}
function computeLogicalDigest(index) {
const descriptors = [...(index.identity_descriptors ?? [])].sort().map((name) => {
const d = index.descriptors?.[name];
return { name, mediaType: d?.mediaType, digest: d?.digest, size: d?.size, path: d?.path };
});
return digestBytes(Buffer.from(stableJson({
identity: PROJECT_ARCHIVE_DIGEST_IDENTITY,
archive_version: index.archive_version,
core_compatibility: index.core_compatibility,
required_capabilities: [...(index.required_capabilities ?? [])].sort(),
consistency: index.consistency ?? null,
descriptors,
}), "utf8"));
}
function countAuthSubjectStubs(index, archive) {
const rel = index.descriptors?.auth_subjects?.path ?? "auth/subjects.ndjson";
const bytes = archive.entries.get(rel);
if (!bytes)
return 0;
return Buffer.from(bytes).toString("utf8").split(/\r?\n/).filter((line) => line.trim()).length;
}
function digestBytes(bytes) {
return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
}
function stableJson(value) {
if (value === undefined)
return "null";
if (value === null || typeof value !== "object")
return JSON.stringify(value);
if (Array.isArray(value))
return `[${value.map(stableJson).join(",")}]`;
const obj = value;
return `{${Object.keys(obj).sort().map((key) => `${JSON.stringify(key)}:${stableJson(obj[key])}`).join(",")}}`;
}
function isArchiveDiagnostic(value) {
return Boolean(value &&
typeof value === "object" &&
!Array.isArray(value) &&
typeof value.code === "string" &&
typeof value.message === "string");
}
function errorToDiagnostic(err) {
const message = err instanceof Error ? err.message : String(err);
const body = err && typeof err === "object" && !Array.isArray(err)
? err.body
: null;
const bodyRecord = body && typeof body === "object" && !Array.isArray(body)
? body
: null;
const details = bodyRecord?.details && typeof bodyRecord.details === "object" && !Array.isArray(bodyRecord.details)
? bodyRecord.details
: undefined;
const code = (bodyRecord && typeof bodyRecord.code === "string" ? bodyRecord.code : null) ??
(err && typeof err === "object" && typeof err.code === "string"
? err.code
: null) ??
"IMPORT_VERIFY_FAILED";
return diagnostic({
code,
resourceType: "archive",
path: typeof details?.path === "string" ? details.path : undefined,
message,
context: details,
});
}
async function extractUncompressedTar(tarPath, destDir) {
const bytes = await readFile(tarPath);
const seen = new Set();
let offset = 0;
while (offset + TAR_BLOCK_BYTES <= bytes.byteLength) {
const header = bytes.subarray(offset, offset + TAR_BLOCK_BYTES);
offset += TAR_BLOCK_BYTES;
if (header.equals(ZERO_BLOCK))
break;
const name = readTarString(header, 0, 100);
const prefix = readTarString(header, 345, 155);
const type = readTarString(header, 156, 1) || "0";
const size = readTarOctal(header, 124, 12);
const rel = sanitizeTarPath(prefix ? `${prefix}/${name}` : name);
if (seen.has(rel)) {
throw new LocalError(`Duplicate archive path while extracting: ${rel}`, "extracting archive tar", {
code: "ARCHIVE_DUPLICATE_PATH",
details: { path: rel },
});
}
seen.add(rel);
const outputPath = path.join(destDir, rel);
if (!outputPath.startsWith(`${destDir}${path.sep}`)) {
throw new LocalError(`Unsafe archive path while extracting: ${rel}`, "extracting archive tar", {
code: "ARCHIVE_PATH_UNSAFE",
details: { path: rel },
});
}
if (type === "5") {
await mkdir(outputPath, { recursive: true });
}
else if (type === "0" || type === "\0") {
const end = offset + size;
if (end > bytes.byteLength) {
throw new LocalError("Malformed tar: entry exceeds file size", "extracting archive tar", {
code: "ARCHIVE_MALFORMED_TAR",
details: { path: rel },
});
}
await mkdir(path.dirname(outputPath), { recursive: true });
await writeFile(outputPath, bytes.subarray(offset, end));
await stat(outputPath).then((s) => {
if (!s.isFile()) {
throw new LocalError(`Archive entry did not extract as a regular file: ${rel}`, "extracting archive tar", {
code: "ARCHIVE_ENTRY_TYPE_UNSUPPORTED",
details: { path: rel },
});
}
});
}
else {
throw new LocalError(`Unsupported tar entry type ${JSON.stringify(type)} at ${rel}`, "extracting archive tar", {
code: "ARCHIVE_ENTRY_TYPE_UNSUPPORTED",
details: { path: rel, type },
});
}
offset += Math.ceil(size / TAR_BLOCK_BYTES) * TAR_BLOCK_BYTES;
}
}
function readTarString(header, start, len) {
const slice = header.subarray(start, start + len);
const nul = slice.indexOf(0);
return slice.subarray(0, nul === -1 ? slice.length : nul).toString("utf8").trim();
}
function readTarOctal(header, start, len) {
const raw = readTarString(header, start, len).replace(/\0/g, "").trim();
if (!raw)
return 0;
if (!/^[0-7]+$/.test(raw)) {
throw new LocalError(`Malformed tar octal value: ${raw}`, "extracting archive tar", {
code: "ARCHIVE_MALFORMED_TAR",
});
}
return Number.parseInt(raw, 8);
}
function sanitizeTarPath(value) {
const normalized = path.posix.normalize(value);
if (!normalized ||
normalized === "." ||
normalized.startsWith("/") ||
normalized.startsWith("../") ||
normalized.includes("/../") ||
/[\x00-\x1f\x7f]/.test(normalized)) {
throw new LocalError(`Unsafe archive path: ${value}`, "extracting archive tar", {
code: "ARCHIVE_PATH_UNSAFE",
details: { path: value },
});
}
return normalized;
}
function parseEnvValue(raw) {
if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
return raw.slice(1, -1);
}
const comment = raw.search(/\s#/);
return (comment === -1 ? raw : raw.slice(0, comment)).trim();
}
function normalizeCoreUrl(value) {
return (value ?? process.env.RUN402_CORE_URL ?? process.env.CORE_GATEWAY_URL ?? DEFAULT_CORE_URL).replace(/\/+$/, "");
}
async function parseJsonBody(res) {
const text = await res.text();
if (!text)
return null;
try {
return JSON.parse(text);
}
catch {
return { error: "NON_JSON_RESPONSE", message: text.slice(0, 500) };
}
}
function isImportResult(value) {
return Boolean(value &&
typeof value === "object" &&
!Array.isArray(value) &&
value.schema_version === "run402.project_archive.import_result.v1");
}
function failedImportResult(input) {
return {
schema_version: "run402.project_archive.import_result.v1",
status: "failed",
archive_digest: input.archiveDigest,
required_secrets: input.requiredSecrets,
diagnostics: input.diagnostics,
next_action: input.nextAction,
};
}
function diagnostic(input) {
return {
code: input.code,
severity: "blocking",
resource_type: input.resourceType,
...(input.resourceId ? { resource_id: input.resourceId } : {}),
...(input.path ? { path: input.path } : {}),
message: input.message,
next_action: { type: "read_docs", message: "Review the archive diagnostic and retry." },
retryable: input.retryable ?? false,
...(input.context ? { context: input.context } : {}),
};
}
function bodyErrorCode(body) {
if (body && typeof body === "object" && !Array.isArray(body)) {
const obj = body;
if (typeof obj.error === "string")
return obj.error;
if (typeof obj.code === "string")
return obj.code;
}
return "IMPORT_CONFORMANCE_FAILED";
}
function bodyErrorMessage(body, fallback) {
if (body && typeof body === "object" && !Array.isArray(body)) {
const obj = body;
if (typeof obj.message === "string")
return obj.message;
if (typeof obj.error === "string")
return obj.error;
}
return fallback;
}
function safeBodyContext(body) {
if (!body || typeof body !== "object" || Array.isArray(body))
return body;
const { details, ...rest } = body;
return {
...rest,
...(details && typeof details === "object" && !Array.isArray(details)
? { details: redactContext(details) }
: {}),
};
}
function redactContext(input) {
const out = {};
for (const [key, value] of Object.entries(input)) {
if (/secret|token|password|credential|private/i.test(key)) {
out[key] = "[redacted]";
}
else {
out[key] = value;
}
}
return out;
}
//# sourceMappingURL=archives-node.js.map