UNPKG

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.

721 lines • 29.6 kB
/** * Type definitions for the unified `apply` primitive (`unified-deploy` * capability, gateway routes `POST /apply/v1/plans` + commit + operations). * * The user-facing inputs accept polymorphic byte sources (strings, Uint8Array, * Blob, web streams, plus the Node-only `fileSetFromDir`). The wire shapes * carry only `ContentRef` objects — the SDK normalizes byte sources into * refs via the CAS content service before issuing the plan request. */ export const ROUTE_HTTP_METHODS = [ "GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", ]; export const ROUTE_PRICING_NETWORKS = [ "mainnet", "testnet", // tenant-lnbtc-priced-routes: the x402 `exact` scheme on Bitcoin Lightning // (`lnbtc`), settled to the organization's own Lightning receiver. "lightning", "lightning-testnet", // tenant-mpp-priced-routes: an MPP Tempo charge in pathUSD on Tempo mainnet // (chain 4217) / Moderato (42431), paid to the organization's payout address. "tempo", "tempo-testnet", ]; export const LIGHTNING_ROUTE_PRICING_NETWORKS = ["lightning", "lightning-testnet"]; export const EMPTY_STATIC_MANIFEST_METADATA = { file_count: 0, total_bytes: 0, cache_classes: {}, cache_class_sources: {}, spa_fallback: null, }; export function normalizeStaticManifestMetadata(metadata) { return metadata ?? EMPTY_STATIC_MANIFEST_METADATA; } export function normalizeDeployResolveRequest(opts) { if (!opts || typeof opts !== "object") { throw new TypeError("Deploy resolve options must be an object"); } const hasUrl = "url" in opts && opts.url !== undefined; const hasHost = "host" in opts && opts.host !== undefined; if (hasUrl === hasHost) { throw new TypeError("Deploy resolve requires exactly one input form: a full absolute url, or a clean host/path pair."); } if (!opts.project || typeof opts.project !== "string") { throw new TypeError("Deploy resolve requires project"); } const method = normalizeDeployResolveMethod(opts.method); if (hasUrl) { const original = String(opts.url); let parsed; try { parsed = opts.url instanceof URL ? opts.url : new URL(original); } catch { throw new TypeError("Deploy resolve url must be an absolute HTTP(S) public URL."); } if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { throw new TypeError("Deploy resolve url must use http: or https:."); } if (parsed.username || parsed.password) { throw new TypeError("Deploy resolve url must not include username or password credentials."); } const ignored = {}; if (parsed.search) ignored.query = parsed.search; if (parsed.hash) ignored.fragment = parsed.hash; return { project: opts.project, project_scope: "credential_lookup_only", project_sent_to_gateway: false, original_url: original, host: parsed.hostname, path: parsed.pathname || "/", ...(method ? { method } : {}), ...(Object.keys(ignored).length > 0 ? { ignored } : {}), }; } const host = opts.host; if (typeof host !== "string" || host.length === 0) { throw new TypeError("Deploy resolve host must be a non-empty hostname."); } if (/\s/.test(host) || host.includes("/") || host.includes("?") || host.includes("#") || /^[a-z][a-z0-9+.-]*:/i.test(host) || host.includes("@")) { throw new TypeError("Deploy resolve host must be a clean hostname without scheme, credentials, path, query, or fragment."); } const path = opts.path; if (path !== undefined) { if (typeof path !== "string" || !path.startsWith("/")) { throw new TypeError("Deploy resolve path must start with '/'."); } if (path.includes("?") || path.includes("#")) { throw new TypeError("Deploy resolve host/path mode does not accept query strings or fragments in path."); } } return { project: opts.project, project_scope: "credential_lookup_only", project_sent_to_gateway: false, host, path: path ?? "/", ...(method ? { method } : {}), }; } export function isDeployResolveStaticHit(response) { return (response.match === "static_exact" || response.match === "static_index" || response.match === "spa_fallback" || response.match === "retained_public_path"); } export function isDeployResolveRouteHit(response) { return !!response.route && typeof response.route === "object"; } export function buildDeployResolveSummary(response, request) { const warnings = deployResolveWarningsForRequest(request, response); const next_steps = deployResolveNextSteps(response); const would_serve = response.authorized === true && isDeployResolveAuthorizationPassing(response) && response.result >= 200 && response.result < 400 && response.match !== "host_missing" && response.match !== "route_method_miss" && !isDeployResolveCasFailure(response); const method = request.method ?? "GET"; const url = `${method} ${request.original_url ? displayResolveUrl(request) : `https://${request.host}${request.path}`}`; const category = deployResolveCategory(response); const summary = withDeploymentHostNote(deployResolveSummaryText(response, url), response); return { would_serve, diagnostic_status: response.result, match: response.match, category, summary, warnings, next_steps, }; } export function summarizeDeployResult(result) { const diff = result.diff ?? {}; const summary = { schema_version: "deploy-summary.v1", release_id: result.release_id, operation_id: result.operation_id, ...(typeof diff.is_noop === "boolean" ? { is_noop: diff.is_noop } : {}), ...(result.static_continuity ? { static_continuity: result.static_continuity } : {}), headline: "", warnings: summarizeDeployWarnings(result.warnings), }; const site = summarizeDeploySite(diff); if (site) summary.site = site; if (isModernFunctionsDiff(diff.functions)) { summary.functions = { added: [...diff.functions.added], removed: [...diff.functions.removed], changed: diff.functions.changed.map((entry) => ({ name: entry.name, fields_changed: [...entry.fields_changed], })), }; } if (isModernPlanMigrationDiff(diff.migrations)) { summary.migrations = { new: diff.migrations.new.map((entry) => entry.id), noop: diff.migrations.noop.map((entry) => entry.id), }; } if (isModernRoutesDiff(diff.routes)) { summary.routes = summarizeResourceCounts(diff.routes); } if (isModernSecretsDiff(diff.secrets)) { summary.secrets = { added: diff.secrets.added.length, removed: diff.secrets.removed.length, }; } if (isModernSubdomainsDiff(diff.subdomains)) { summary.subdomains = { added: diff.subdomains.added.length, removed: diff.subdomains.removed.length, }; } summary.headline = buildDeploySummaryHeadline(summary); return summary; } function summarizeDeployWarnings(warnings) { const codes = Array.from(new Set(warnings.map((warning) => warning.code))).sort(); return { count: warnings.length, blocking: warnings.filter((warning) => warning.requires_confirmation || warning.code === "MISSING_REQUIRED_SECRET").length, codes, }; } function summarizeDeploySite(diff) { const out = {}; if (isStaticAssetsDiff(diff.static_assets)) { out.paths = { added: diff.static_assets.added, changed: diff.static_assets.changed, removed: diff.static_assets.removed, unchanged: diff.static_assets.unchanged, total_changed: diff.static_assets.added + diff.static_assets.changed + diff.static_assets.removed, }; out.cas = { newly_uploaded_bytes: diff.static_assets.newly_uploaded_cas_bytes, reused_bytes: diff.static_assets.reused_cas_bytes, deployment_copy_bytes_eliminated: diff.static_assets.deployment_copy_bytes_eliminated, }; } else if (isModernSiteDiff(diff.site)) { const added = diff.site.totals?.added ?? diff.site.added.length; const changed = diff.site.totals?.changed ?? diff.site.changed.length; const removed = diff.site.totals?.removed ?? diff.site.removed.length; out.paths = { added, changed, removed, total_changed: added + changed + removed, }; } return out.paths || out.cas ? out : undefined; } function summarizeResourceCounts(diff) { const added = diff.totals?.added ?? diff.added.length; const changed = diff.totals?.changed ?? diff.changed.length; const removed = diff.totals?.removed ?? diff.removed.length; return { added, changed, removed }; } function isStaticAssetsDiff(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return false; const obj = value; return (typeof obj.unchanged === "number" && typeof obj.changed === "number" && typeof obj.added === "number" && typeof obj.removed === "number" && typeof obj.newly_uploaded_cas_bytes === "number" && typeof obj.reused_cas_bytes === "number" && typeof obj.deployment_copy_bytes_eliminated === "number"); } function isModernSiteDiff(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return false; const obj = value; return (Array.isArray(obj.added) && Array.isArray(obj.changed) && Array.isArray(obj.removed)); } function isModernFunctionsDiff(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return false; const obj = value; return (Array.isArray(obj.added) && Array.isArray(obj.removed) && Array.isArray(obj.changed)); } export function isModernPlanMigrationDiff(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return false; const obj = value; return Array.isArray(obj.new) && Array.isArray(obj.noop); } function isModernRoutesDiff(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return false; const obj = value; return Array.isArray(obj.added) && Array.isArray(obj.changed) && Array.isArray(obj.removed); } function isModernSecretsDiff(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return false; const obj = value; return Array.isArray(obj.added) && Array.isArray(obj.removed); } function isModernSubdomainsDiff(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return false; const obj = value; return Array.isArray(obj.added) && Array.isArray(obj.removed); } function buildDeploySummaryHeadline(summary) { const parts = []; const paths = summary.site?.paths; if (paths) { parts.push(paths.total_changed === 0 ? "no static path changes" : `${formatCount(paths.total_changed, "static path")} changed`); } const cas = summary.site?.cas; if (cas) { parts.push(`${formatBytes(cas.newly_uploaded_bytes)} uploaded, ${formatBytes(cas.reused_bytes)} reused`); } const functions = summary.functions; if (functions) { const count = functions.added.length + functions.removed.length + functions.changed.length; parts.push(count === 0 ? "no functions changed" : `${formatCount(count, "function")} changed`); } const migrations = summary.migrations; if (migrations && migrations.new.length > 0) { parts.push(`${formatCount(migrations.new.length, "migration")} new`); } const routes = summary.routes; if (routes) { const count = routes.added + routes.changed + routes.removed; if (count > 0) parts.push(`${formatCount(count, "route")} changed`); } if (parts.length > 0) return parts.join("; "); if (summary.is_noop === true) return "no deploy changes reported"; return "deploy summary unavailable"; } function formatCount(count, singular) { return `${count} ${singular}${count === 1 ? "" : "s"}`; } function formatBytes(bytes) { if (!Number.isFinite(bytes)) return `${bytes} B`; const units = ["B", "KB", "MB", "GB", "TB"]; let value = bytes; let unitIndex = 0; while (Math.abs(value) >= 1000 && unitIndex < units.length - 1) { value /= 1000; unitIndex += 1; } if (unitIndex === 0) return `${bytes} B`; return `${value.toFixed(1)} ${units[unitIndex]}`; } function normalizeDeployResolveMethod(method) { if (method === undefined) return undefined; if (typeof method !== "string") { throw new TypeError("Deploy resolve method must be a string."); } const normalized = method.trim().toUpperCase(); if (!/^[A-Z][A-Z0-9!#$%&'*+.^_`|~-]*$/.test(normalized)) { throw new TypeError("Deploy resolve method must be a valid HTTP token such as GET, HEAD, POST, or OPTIONS."); } return normalized; } function displayResolveUrl(request) { if (!request.original_url) return `https://${request.host}${request.path}`; try { const url = new URL(request.original_url); return `${url.protocol}//${url.host}${url.pathname || "/"}`; } catch { return `${request.host}${request.path}`; } } function deployResolveWarningsForRequest(request, response) { const warnings = []; if (request.ignored?.query) { warnings.push({ code: "query_ignored", message: "Query strings do not affect Run402 route resolution.", }); } if (request.ignored?.fragment) { warnings.push({ code: "fragment_ignored", message: "URL fragments are never sent to the server and do not affect resolution.", }); } const edge = response?.edge_propagation; if (edge && edge.status !== "settled") { warnings.push({ code: `edge_${edge.status}`, message: edge.hint, }); } return warnings; } function deployResolveCategory(response) { if (isDeployResolveCasFailure(response)) return "cas"; if (response.match === "route_method_miss") return "route_method"; if (response.match === "active_release_missing") return "release"; if (response.match === "unsupported_manifest_version") return "manifest"; if (isDeployResolveRouteHit(response)) return "route"; if (response.match === "route_function" || response.match === "route_static_alias") { return "route"; } if (isDeployResolveStaticHit(response)) return "static"; if (response.match === "host_missing") return "host"; if (response.match === "deployment_host" || response.match === "deployment_retired") return "deployment"; if (response.match === "manifest_missing") return "manifest"; if (response.match === "path_error") return "path"; if (response.match === "spa_fallback_missing") return "fallback"; if (response.match === "none") return "miss"; return "unknown"; } function withDeploymentHostNote(summary, response) { if (response.host_kind !== "deployment" || response.path_evaluated !== true || response.deployment_status !== "retained" || !response.origin_available_until) { return summary; } const routes = response.routes_active === false ? " Its routes no longer run." : ""; return `${summary} This deployment host was replaced and serves until ${response.origin_available_until}, then stops.${routes}`; } function deployResolveSummaryText(response, url) { if (isDeployResolveCasFailure(response)) { const sha = response.cas_object?.sha256; const suffix = sha ? ` for CAS object ${sha}` : ""; switch (response.authorization_result) { case "missing_cas_object": return `${url} matched static content, but the backing CAS object is missing${suffix}.`; case "unfinalized_or_deleting_cas_object": return `${url} matched static content, but the backing CAS object is not finalized or is being deleted${suffix}.`; case "size_mismatch": return `${url} matched static content, but the backing CAS object size does not match the release manifest${suffix}.`; case "unauthorized_cas_object": return `${url} matched static content, but the selected credentials are not authorized to inspect the backing CAS object${suffix}.`; default: return `${url} matched static content, but CAS object health prevented a servable diagnostic${suffix}.`; } } if (response.match === "host_missing") { return `${url} did not resolve because the host is not bound to this account/project context.`; } if (response.match === "manifest_missing") { return `${url} reached the host, but no active static manifest is available for diagnostics.`; } if (response.match === "deployment_host") { const deployment = response.deployment_id ?? "its deployment"; return response.deployment_status === "retained" && response.origin_available_until ? `${url} is a deployment host for ${deployment}, which was replaced; it serves the whole deployment until ${response.origin_available_until}, then stops. The path itself is not evaluated.` : `${url} is a deployment host serving ${deployment}. The path itself is not evaluated.`; } if (response.match === "deployment_retired") { return `${url} is a deployment host for ${response.deployment_id ?? "a deployment"} that was replaced and is no longer served.`; } if (response.match === "active_release_missing") { return `${url} reached the host, but no active release is available for diagnostics.`; } if (response.match === "unsupported_manifest_version" || response.authorization_result === "unsupported_manifest_version") { return `${url} reached the host, but the active static manifest version is not supported by the gateway.`; } if (response.match === "path_error") { return `${url} could not be evaluated because the path is not a valid Run402 public path.`; } if (response.match === "route_method_miss") { const allowed = formatAllowedMethods(response.allow); return allowed ? `${url} matched a deploy route pattern, but the method is not allowed. Allowed methods: ${allowed}.` : `${url} matched a deploy route pattern, but the method is not allowed.`; } if (response.match === "none") { return `${url} did not match a materialized static file or SPA fallback.`; } if (response.match === "spa_fallback_missing") { return `${url} was eligible for SPA fallback, but the configured fallback file is missing.`; } if (response.match === "spa_fallback") { return `${url} would serve the configured SPA fallback.`; } if (response.match === "static_index") { return `${url} would serve a static index file.`; } if (response.match === "static_exact") { return `${url} would serve an exact static file.`; } if (response.match === "route_function") { return `${url} matched a deploy function route.`; } if (response.match === "route_static_alias") { return `${url} matched a deploy static route alias.`; } if (isDeployResolveRouteHit(response)) { return `${url} matched a deploy route.`; } if (response.result >= 200 && response.result < 400) { return `${url} resolved with gateway match ${String(response.match)}.`; } return `${url} did not resolve to a servable public response; gateway match was ${String(response.match)}.`; } function deployResolveNextSteps(response) { const edge = response.edge_propagation; if (edge && edge.status !== "settled") { return [ { code: edge.status === "sync_pending" ? "retry_after_edge_sync" : "retry_after_edge_propagation", message: edge.status === "sync_pending" ? "Retry after the edge-store source write is confirmed; the Run402 reconciler will rewrite the binding." : "Retry after edge propagation settles, or run `run402 up verify` for the app workspace.", }, { code: "inspect_edge_propagation", message: "Inspect resolution.edge_propagation for claimed_at, kvs_synced_at, expected_visible_by, and hint.", }, ]; } if (isDeployResolveCasFailure(response)) { switch (response.authorization_result) { case "unauthorized_cas_object": return [ { code: "check_credentials", message: "Check that the selected project credentials can inspect the backing static asset CAS object.", }, { code: "inspect_release_asset", message: "Inspect the active release asset path and redeploy the affected static asset if the CAS authorization is unexpected.", }, ]; case "missing_cas_object": return [ { code: "redeploy_static_asset", message: "Redeploy the affected static asset so the backing CAS object is uploaded and finalized.", }, { code: "inspect_release_asset", message: "Inspect the active release asset path and CAS SHA in the full resolution payload.", }, ]; case "unfinalized_or_deleting_cas_object": return [ { code: "retry_after_cas_finalization", message: "Retry diagnostics after the backing CAS object has finalized, or redeploy the affected static asset.", }, { code: "inspect_release_asset", message: "Inspect the active release asset path and CAS SHA in the full resolution payload.", }, ]; case "size_mismatch": return [ { code: "redeploy_static_asset", message: "Redeploy the affected static asset so release metadata and CAS object size agree.", }, { code: "inspect_cas_object", message: "Compare cas_object.expected_size and cas_object.actual_size in the full resolution payload.", }, ]; default: return [ { code: "inspect_cas_object", message: "Inspect authorization_result and cas_object in the full resolution payload.", }, { code: "redeploy_static_asset", message: "Redeploy the affected static asset if the CAS health failure persists.", }, ]; } } switch (response.match) { case "host_missing": return [ { code: "check_domain_binding", message: "Check that the host is configured as a Run402 custom domain or subdomain.", }, { code: "check_dns", message: "Check DNS and domain binding status.", }, { code: "check_credentials", message: "Check that the selected local project credentials can inspect this host.", }, ]; case "deployment_retired": return [ { code: "use_project_host", message: "Use the project's subdomain or custom domain, which serves the live release.", }, { code: "promote_release", message: "Promote this deployment's release (`run402 deploy promote <release_id>`) only if it should be live again.", }, ]; case "manifest_missing": return [ { code: "check_active_release", message: "Check that the project has an active apply-v1 release.", }, { code: "redeploy_static_site", message: "Deploy static site content again if the active release predates static manifest metadata.", }, ]; case "active_release_missing": return [ { code: "check_active_release", message: "Check that the project has an active apply-v1 release.", }, { code: "deploy_release", message: "Deploy site content or routes before diagnosing this public URL.", }, ]; case "unsupported_manifest_version": return [ { code: "redeploy_static_site", message: "Redeploy static site content so the active release has a current static manifest.", }, { code: "inspect_release", message: "Inspect the active release static_manifest_sha256 and retry diagnostics after redeploy.", }, ]; case "path_error": return [ { code: "check_public_path", message: "Use an absolute URL path that starts with '/' and does not contain invalid encoded segments.", }, ]; case "none": return [ { code: "check_static_path", message: "Check active release static_public_paths for browser reachability and site.paths for backing release assets.", }, { code: "check_spa_fallback", message: "Check whether the release has a configured SPA fallback.", }, ]; case "spa_fallback_missing": return [ { code: "restore_spa_fallback", message: "Deploy the configured SPA fallback file or remove the fallback declaration.", }, ]; case "route_method_miss": { const allowed = formatAllowedMethods(response.allow); return [ { code: "check_route_methods", message: allowed ? `Retry with one of the allowed methods (${allowed}) or update the route method list.` : "Inspect the route method list and retry with an allowed method or update the route.", }, { code: "inspect_route", message: "Inspect route_pattern, target_type, target_name, and target_file in the full resolution payload.", }, ]; } default: return response.result >= 200 && response.result < 400 ? [] : [ { code: "inspect_resolution", message: "Inspect the full resolution payload for gateway-specific fields.", }, ]; } } function isDeployResolveAuthorizationPassing(response) { const result = response.authorization_result; return result === undefined || result === null || result === "authorized" || result === "not_applicable"; } function isDeployResolveCasFailure(response) { switch (response.authorization_result) { case "missing_cas_object": case "unfinalized_or_deleting_cas_object": case "size_mismatch": case "unauthorized_cas_object": return true; default: break; } const cas = response.cas_object; if (!cas) return false; if (cas.exists === false) return true; return typeof cas.actual_size === "number" && cas.actual_size !== cas.expected_size; } function formatAllowedMethods(methods) { if (!methods || methods.length === 0) return ""; return methods.join(", "); } //# sourceMappingURL=deploy.types.js.map