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.
1,182 lines (1,181 loc) • 31.5 kB
JSON
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://run402.com/schemas/release-spec.v1.json",
"title": "Run402 ReleaseSpec v1",
"description": "Authoring schema for Run402 deploy manifests. Use run402 up for complete application workflows. Project selection may be supplied by an explicit CLI target, app-local link or approved --name creation; the SDK requires a resolved project before gateway planning.",
"type": "object",
"additionalProperties": false,
"properties": {
"$schema": {
"type": "string",
"description": "Editor metadata only. Manifest adapters strip this before deploy planning."
},
"x-run402-omitted_features": {
"type": "array",
"description": "App-kit evidence metadata for humans/agents. Manifest adapters preserve it in the loaded manifest and strip it before deploy planning."
},
"project_id": {
"type": "string",
"description": "The target project id. The one spelling in a manifest, in a typed ReleaseSpec, and on the wire."
},
"idempotency_key": {
"type": "string"
},
"idempotencyKey": {
"type": "string"
},
"base": {
"$ref": "#/$defs/base"
},
"database": {
"$ref": "#/$defs/database"
},
"secrets": {
"$ref": "#/$defs/secrets"
},
"functions": {
"$ref": "#/$defs/functions"
},
"site": {
"$ref": "#/$defs/site"
},
"assets": {
"$ref": "#/$defs/assets"
},
"subdomains": {
"$ref": "#/$defs/subdomains"
},
"routes": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/$defs/routes"
}
]
},
"checks": {
"type": "array",
"items": {
"$ref": "#/$defs/smokeCheck"
}
},
"i18n": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/$defs/i18n"
}
]
},
"verify": {
"$ref": "#/$defs/verify",
"description": "Authoring-only post-apply HTTP verification, run by `run402 up` after a successful apply and rerunnable with `run402 up verify`. Stripped before deploy planning (never part of the wire ReleaseSpec)."
}
},
"$defs": {
"base": {
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"required": [
"release"
],
"properties": {
"release": {
"enum": [
"current",
"empty"
]
}
}
},
{
"type": "object",
"additionalProperties": false,
"required": [
"release_id"
],
"properties": {
"release_id": {
"type": "string"
}
}
}
]
},
"database": {
"type": "object",
"additionalProperties": false,
"properties": {
"migrations": {
"type": "array",
"items": {
"$ref": "#/$defs/migration"
}
},
"expose": {
"type": "object",
"description": "Authorization/expose manifest. See https://run402.com/schemas/manifest.v1.json for its schema."
},
"zero_downtime": {
"type": "boolean"
}
}
},
"migration": {
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"description": "Versioned immutable migration id, e.g. 001_init. Same id plus same checksum noops; same id plus different SQL is MIGRATION_CHECKSUM_MISMATCH. Use name instead for generated/idempotent SQL whose identity should track content changes."
},
"name": {
"type": "string",
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$",
"description": "Content-tracked migration name for generated/idempotent SQL. The SDK compiles this to <name>_<sha256(sql)[0:16]>; changed content applies once under a new id and identical re-deploys noop. SQL declared with name MUST be idempotent because it re-runs whenever content changes against a database where prior versions may already exist."
},
"checksum": {
"type": "string"
},
"sql": {
"type": "string"
},
"sql_ref": {
"$ref": "#/$defs/contentRef"
},
"sql_path": {
"type": "string"
},
"sql_file": {
"type": "string"
},
"transaction": {
"enum": [
"required",
"none"
]
}
},
"oneOf": [
{
"required": [
"id"
],
"not": {
"required": [
"name"
]
}
},
{
"required": [
"name"
],
"not": {
"required": [
"id"
]
}
}
]
},
"secrets": {
"type": "object",
"additionalProperties": false,
"properties": {
"require": {
"type": "array",
"items": {
"type": "string"
}
},
"delete": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"functions": {
"type": "object",
"additionalProperties": false,
"properties": {
"replace": {
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/functionSpec"
}
},
"patch": {
"type": "object",
"additionalProperties": false,
"properties": {
"set": {
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/functionSpec"
}
},
"delete": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"functionSpec": {
"type": "object",
"additionalProperties": false,
"description": "FunctionSpec. schedule is a sibling of runtime/source/files/config, not config.schedule.",
"properties": {
"runtime": {
"enum": [
"node22"
]
},
"source": {
"$ref": "#/$defs/fileEntry"
},
"files": {
"$ref": "#/$defs/fileSet"
},
"entrypoint": {
"type": "string"
},
"config": {
"type": "object",
"additionalProperties": false,
"properties": {
"timeoutSeconds": {
"type": "integer",
"minimum": 1
},
"memoryMb": {
"type": "integer",
"minimum": 1
}
}
},
"schedule": {
"oneOf": [
{
"type": "string",
"description": "5-field cron expression."
},
{
"type": "null",
"description": "Remove an existing schedule in patch mode."
}
]
},
"deps": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"triggers": {
"type": "array",
"items": {
"$ref": "#/$defs/functionTrigger"
}
},
"requireAuth": {
"type": "boolean"
},
"require_auth": {
"type": "boolean"
},
"requireRole": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/$defs/functionRequireRole"
}
]
},
"require_role": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/$defs/functionRequireRole"
}
]
},
"class": { "enum": ["ssr", "standard"] },
"capabilities": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
}
}
},
"functionTrigger": {
"type": "object",
"additionalProperties": true,
"required": [
"id",
"type"
],
"properties": {
"id": {
"type": "string",
"minLength": 1
},
"type": {
"type": "string",
"minLength": 1
}
}
},
"functionRequireRole": {
"type": "object",
"additionalProperties": false,
"required": [
"table",
"id_column",
"role_column",
"allowed"
],
"properties": {
"table": {
"type": "string",
"minLength": 1
},
"id_column": {
"type": "string",
"minLength": 1
},
"role_column": {
"type": "string",
"minLength": 1
},
"allowed": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"minItems": 1
},
"cache_ttl": {
"type": "integer",
"minimum": 0
},
"on_deny": {
"enum": [
"envelope",
"redirect"
]
},
"sign_in_path": {
"type": "string",
"minLength": 1
}
}
},
"site": {
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"required": [
"replace"
],
"properties": {
"replace": {
"oneOf": [
{
"$ref": "#/$defs/fileSet"
},
{
"$ref": "#/$defs/localDirRef"
}
]
},
"public_paths": {
"$ref": "#/$defs/sitePublicPaths"
},
"embedding": {
"$ref": "#/$defs/siteEmbedding"
},
"sign_in_path": {
"$ref": "#/$defs/siteSignInPath"
},
"noindex": {
"$ref": "#/$defs/siteNoindex"
}
}
},
{
"type": "object",
"additionalProperties": false,
"required": [
"patch"
],
"properties": {
"patch": {
"type": "object",
"additionalProperties": false,
"properties": {
"put": {
"oneOf": [
{
"$ref": "#/$defs/fileSet"
},
{
"$ref": "#/$defs/localDirRef"
}
]
},
"delete": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"public_paths": {
"$ref": "#/$defs/sitePublicPaths"
},
"embedding": {
"$ref": "#/$defs/siteEmbedding"
},
"sign_in_path": {
"$ref": "#/$defs/siteSignInPath"
},
"noindex": {
"$ref": "#/$defs/siteNoindex"
}
}
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"public_paths": {
"$ref": "#/$defs/sitePublicPaths"
},
"embedding": {
"$ref": "#/$defs/siteEmbedding"
},
"sign_in_path": {
"$ref": "#/$defs/siteSignInPath"
},
"noindex": {
"$ref": "#/$defs/siteNoindex"
}
},
"anyOf": [
{
"required": [
"public_paths"
]
},
{
"required": [
"embedding"
]
},
{
"required": [
"sign_in_path"
]
},
{
"required": [
"noindex"
]
}
]
}
]
},
"sitePublicPaths": {
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"required": [
"mode"
],
"properties": {
"mode": {
"const": "implicit"
}
}
},
{
"type": "object",
"additionalProperties": false,
"required": [
"mode",
"replace"
],
"properties": {
"mode": {
"const": "explicit"
},
"replace": {
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/publicStaticPath"
}
}
}
}
]
},
"publicStaticPath": {
"type": "object",
"additionalProperties": false,
"required": [
"asset"
],
"properties": {
"asset": {
"type": "string",
"minLength": 1,
"description": "Release static asset path, not a public URL."
},
"cache_class": {
"$ref": "#/$defs/staticCacheClass"
}
}
},
"staticCacheClass": {
"type": "string",
"description": "Known values: html, immutable_versioned, revalidating_asset. Unknown future strings may be returned by observability APIs and should be preserved."
},
"subdomains": {
"type": "object",
"additionalProperties": false,
"properties": {
"set": {
"type": "array",
"items": {
"type": "string"
}
},
"add": {
"type": "array",
"items": {
"type": "string"
}
},
"remove": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"routes": {
"type": "object",
"additionalProperties": false,
"required": [
"replace"
],
"properties": {
"replace": {
"type": "array",
"items": {
"$ref": "#/$defs/route"
}
}
}
},
"route": {
"type": "object",
"additionalProperties": false,
"required": [
"pattern",
"target"
],
"properties": {
"pattern": {
"type": "string"
},
"methods": {
"type": "array",
"minItems": 1,
"items": {
"enum": [
"GET",
"HEAD",
"POST",
"PUT",
"PATCH",
"DELETE",
"OPTIONS"
]
}
},
"target": {
"oneOf": [
{
"$ref": "#/$defs/functionRouteTarget"
},
{
"$ref": "#/$defs/staticRouteTarget"
}
]
},
"pricing": {
"$ref": "#/$defs/routePricing"
}
},
"allOf": [
{
"if": {
"properties": {
"target": {
"type": "object",
"properties": {
"type": {
"const": "static"
}
},
"required": [
"type"
]
}
},
"required": [
"target"
]
},
"then": {
"not": {
"required": [
"pricing"
]
}
}
}
]
},
"routePricingDiscovery": {
"type": "object",
"additionalProperties": false,
"required": ["description"],
"description": "Opt-in x402 Bazaar listing for this priced route. Exact route with exactly one method (GET may include HEAD); at most 4096 bytes of canonical JSON. The 402 challenge carries it plus the run402 tag and accepts[].extra.hosted_by.",
"properties": {
"description": { "type": "string", "minLength": 1, "maxLength": 500, "description": "What the endpoint does and when to call it; used verbatim." },
"mime_type": { "type": "string", "description": "Response media type. Default application/json." },
"service_name": { "type": "string", "minLength": 1, "maxLength": 32, "pattern": "^[\\x20-\\x7e]+$" },
"tags": {
"type": "array",
"maxItems": 4,
"uniqueItems": true,
"items": { "type": "string", "minLength": 1, "maxLength": 32, "pattern": "^[\\x20-\\x7e]+$" },
"description": "At most 4; the Bazaar keeps five and Run402 appends run402."
},
"icon_url": { "type": "string", "format": "uri", "pattern": "^https://", "maxLength": 2048, "description": "Absolute https URL on a public host name, without credentials." },
"input": {
"type": "object",
"additionalProperties": false,
"description": "GET/HEAD/DELETE: query_params_schema and an object example. POST/PUT/PATCH: body_type (required), body_schema, example. Schemas may reference only local # fragments.",
"properties": {
"query_params_schema": { "type": "object" },
"body_type": { "enum": ["json", "form-data", "text"] },
"body_schema": { "type": "object" },
"example": {}
}
},
"output": {
"type": "object",
"additionalProperties": false,
"properties": {
"example": {},
"schema": { "type": "object" }
}
}
}
},
"routePricing": {
"type": "object",
"additionalProperties": false,
"required": [
"mode",
"amount_usd_micros",
"pay_to"
],
"properties": {
"discovery": { "$ref": "#/$defs/routePricingDiscovery" },
"mode": {
"const": "always"
},
"amount_usd_micros": {
"type": "integer",
"minimum": 1,
"maximum": 9007199254740991
},
"amount_msat": {
"type": "integer",
"minimum": 1,
"maximum": 9007199254740991,
"description": "Pins the Lightning price in millisatoshis for `lightning` / `lightning-testnet` entries; needs a Lightning network. Absent, each challenge quotes `amount_usd_micros` into whole satoshis."
},
"pay_to": {
"const": "org_default_payout"
},
"networks": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"default": [
"mainnet"
],
"items": {
"enum": [
"mainnet",
"testnet",
"lightning",
"lightning-testnet",
"tempo",
"tempo-testnet"
]
},
"description": "Omit to accept production mainnet only. Include testnet explicitly for testnet payments. `lightning` / `lightning-testnet` sell for sats over x402 `exact` on `lnbtc`, settled to the org's Lightning receiver (`run402 orgs lightning-receiver set`). `tempo` / `tempo-testnet` also take an MPP Tempo charge in pathUSD on Tempo mainnet (chain 4217) / Moderato (42431), paid to the org's payout address. `receipt` cannot be combined with Lightning or Tempo networks."
}
}
},
"functionRouteTarget": {
"type": "object",
"additionalProperties": false,
"required": [
"type",
"name"
],
"properties": {
"type": {
"const": "function"
},
"name": {
"type": "string"
}
}
},
"staticRouteTarget": {
"type": "object",
"additionalProperties": false,
"required": [
"type",
"file"
],
"properties": {
"type": {
"const": "static"
},
"file": {
"type": "string",
"description": "Relative materialized release static asset path, not a public path."
}
}
},
"smokeCheck": {
"type": "object",
"additionalProperties": true
},
"i18n": {
"type": "object",
"additionalProperties": false,
"required": [
"defaultLocale",
"locales"
],
"description": "Routed-locale-context release slice (v2.5+). Omit at the top level to carry forward from the base release; pass top-level i18n: null to clear; pass an object to replace. defaultLocale MUST be byte-identical to one entry in locales[].",
"properties": {
"defaultLocale": {
"type": "string",
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$",
"description": "Default locale tag. MUST be byte-identical to one entry in locales[]."
},
"locales": {
"type": "array",
"minItems": 1,
"maxItems": 50,
"items": {
"type": "string",
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"
},
"description": "Supported locale tags. Non-empty, max 50 entries. Tags are opaque — only the safety regex is enforced (no BCP-47 semantic validation)."
},
"detect": {
"type": "array",
"maxItems": 10,
"items": {
"type": "string",
"pattern": "^(accept-language|cookie:[!#$%&'*+\\-.^_`|~0-9A-Za-z]+)$"
},
"description": "Walked in order; first match wins. Defaults to ['accept-language'] when omitted, max 10 entries; [] is allowed and means 'always default'. Sources: 'accept-language' and 'cookie:<name>' (RFC 6265 cookie-name grammar)."
},
"unknownLocalePolicy": {
"type": "string",
"enum": [
"reject",
"pass-through"
],
"description": "What to do when a detect-source signal does not match locales[]. 'reject' (default, backwards-compatible) falls through to the next detect source then to defaultLocale. 'pass-through' returns the lowercased, trimmed signal value verbatim — letting the consumer's app DB decide whether translations exist for the tag. Capability i18n-unknown-locale-policy (issue #413)."
}
}
},
"fileSet": {
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/fileEntry"
}
},
"localDirRef": {
"type": "object",
"additionalProperties": false,
"required": [
"__source",
"path"
],
"properties": {
"__source": {
"const": "local-dir"
},
"path": {
"type": "string",
"description": "Local directory path resolved by the SDK/CLI from the manifest directory and stripped before the apply request."
}
},
"description": "CLI/MCP/Node SDK authoring form for static site directories. This is never sent to the gateway wire API."
},
"fileEntry": {
"oneOf": [
{
"type": "string"
},
{
"$ref": "#/$defs/contentRef"
},
{
"type": "object",
"additionalProperties": false,
"required": [
"path"
],
"properties": {
"path": {
"type": "string"
},
"content_type": {
"type": "string"
}
}
},
{
"type": "object",
"additionalProperties": false,
"required": [
"data"
],
"properties": {
"data": {
"oneOf": [
{
"type": "string"
},
{
"$ref": "#/$defs/contentRef"
}
]
},
"encoding": {
"enum": [
"utf-8",
"base64"
]
},
"content_type": {
"type": "string"
}
}
}
]
},
"contentRef": {
"type": "object",
"additionalProperties": false,
"required": [
"sha256",
"size"
],
"properties": {
"sha256": {
"type": "string"
},
"size": {
"type": "integer",
"minimum": 0
},
"content_type": {
"type": "string"
},
"contentType": {
"type": "string",
"description": "Legacy SDK ContentRef spelling accepted by the normalizer; prefer content_type in manifests."
},
"integrity": {
"type": "string"
}
}
},
"assets": {
"type": "object",
"description": "v1.48 unified-apply asset slice. Per-key assets are committed inside the same activation transaction as functions/site/secrets so a release flips atomically.",
"additionalProperties": false,
"properties": {
"put": {
"type": "array",
"items": {
"$ref": "#/$defs/assetPutEntry"
}
},
"delete": {
"type": "array",
"items": {
"type": "string"
},
"description": "Asset keys to remove at activation."
},
"sync": {
"$ref": "#/$defs/assetSync"
}
}
},
"assetPutEntry": {
"type": "object",
"additionalProperties": false,
"required": [
"key"
],
"properties": {
"key": {
"type": "string",
"description": "Asset key under the project's asset namespace (no leading slash)."
},
"source": {
"$ref": "#/$defs/fileEntry",
"description": "SDK-input form. Mutually exclusive with sha256/size_bytes; the SDK normalizer hashes + uploads via /content/v1/plans."
},
"sha256": {
"type": "string",
"description": "Pre-uploaded CAS reference (wire form). Mutually exclusive with source."
},
"size_bytes": {
"type": "integer",
"minimum": 0,
"description": "Required when using the wire form (sha256 set)."
},
"content_type": {
"type": "string"
},
"visibility": {
"enum": [
"public",
"private"
]
},
"immutable": {
"type": "boolean"
}
}
},
"assetSync": {
"type": "object",
"additionalProperties": false,
"required": [
"prefix",
"prune"
],
"properties": {
"prefix": {
"type": "string",
"description": "Prefix under which destructive sync operates."
},
"prune": {
"const": true
},
"confirm": {
"type": "object",
"additionalProperties": false,
"required": [
"base_revision",
"delete_set_digest",
"expected_delete_count"
],
"properties": {
"base_revision": {
"type": "string"
},
"delete_set_digest": {
"type": "string"
},
"expected_delete_count": {
"type": "integer",
"minimum": 0
}
},
"description": "Confirmation token echoed back from a prior plan; required to commit a destructive sync."
}
}
},
"verify": {
"type": "object",
"additionalProperties": false,
"properties": {
"http": {
"type": "array",
"items": {
"$ref": "#/$defs/verifyHttpCheck"
}
}
}
},
"verifyHttpCheck": {
"type": "object",
"additionalProperties": false,
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"minLength": 1
},
"path": {
"type": "string",
"minLength": 1,
"description": "Request path resolved against the project public origin."
},
"url": {
"type": "string",
"minLength": 1
},
"expect": {
"type": "object",
"additionalProperties": false,
"required": [
"status"
],
"properties": {
"status": {
"type": "integer",
"minimum": 100,
"maximum": 599
}
}
},
"expected_status": {
"type": "integer",
"minimum": 100,
"maximum": 599,
"description": "Snake-case alias for expect.status."
},
"retries": {
"type": "integer",
"minimum": 1
}
},
"allOf": [
{
"anyOf": [
{
"required": [
"path"
]
},
{
"required": [
"url"
]
}
]
},
{
"anyOf": [
{
"required": [
"expect"
]
},
{
"required": [
"expected_status"
]
}
]
}
]
},
"siteSignInPath": {
"description": "The app's own same-origin sign-in page (e.g. \"/join\") that MCP authorization sends a signed-out user to, with returnTo appended, instead of the hosted /auth/sign-in. Not under /_run402/ or /auth/; no query or fragment. Remote validation is authoritative. Null resets to the hosted page; omission carries prior state.",
"oneOf": [
{
"type": "null"
},
{
"type": "string",
"maxLength": 512,
"pattern": "^/(?!/)(?!_run402(?:/|$))(?!auth/)(?!.*://)[^?#\\\\\\s\\u0000-\\u001f\\u007f]*$"
}
]
},
"siteNoindex": {
"description": "true adds X-Robots-Tag: noindex, nofollow, nosnippet to every response of the host. false or null resets to indexable (the default); omission carries prior state.",
"type": [
"boolean",
"null"
]
},
"siteEmbedding": {
"description": "Framing opt-in using platform catalog keys, never origins. Remote validation checks the current catalog. Null resets to deny; omission carries prior state.",
"oneOf": [
{
"type": "null"
},
{
"type": "object",
"additionalProperties": false,
"required": [
"frame_ancestors"
],
"properties": {
"frame_ancestors": {
"type": "array",
"uniqueItems": true,
"items": {
"type": "string",
"pattern": "^[a-z][a-z0-9_-]*$"
}
}
}
}
]
}
}
}