{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://toolspace.yepgent.com/schemas/install-manifest-v0.2.json",
  "title": "Agent Tool Install Manifest",
  "description": "v0.2 — A self-describing contract that lets an autonomous agent install, verify, and revoke a third-party tool without human intervention beyond credential disclosure. Adds a top-level `actions[]` catalog so non-MCP tools (Python modules, shell binaries, HTTP endpoints, containers) can be driven structurally without reading prose. v0.1 schema URL is frozen; this schema lives at its own URL.",
  "type": "object",
  "required": ["manifest_version", "tool", "runtime", "smoke", "kill_switch"],
  "additionalProperties": false,
  "properties": {
    "manifest_version": {
      "type": "string",
      "const": "0.2",
      "description": "Version of THIS schema. Pinned. Future schemas bump."
    },

    "tool": {
      "type": "object",
      "description": "Identity of the tool being installed. Stable across versions of the tool itself.",
      "required": ["id", "version", "name", "summary", "homepage"],
      "additionalProperties": false,
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$",
          "description": "Globally unique tool ID. Lowercase, hyphenated. Acts as the registry primary key. Example: 'gmail-yep'."
        },
        "version": {
          "type": "string",
          "pattern": "^\\d+\\.\\d+\\.\\d+(-[a-z0-9.-]+)?$",
          "description": "SemVer of the tool itself. Manifest contract is at manifest_version; this is the tool's published version."
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 80,
          "description": "Human-readable display name."
        },
        "summary": {
          "type": "string",
          "minLength": 1,
          "maxLength": 280,
          "description": "One-sentence description. Shown to the agent during search and to the human during install confirmation."
        },
        "description": {
          "type": "string",
          "maxLength": 4000,
          "description": "Optional longer description. Markdown allowed. Agents should not parse this for behavior; it's for humans. For agent-readable per-action prose, see actions[].description."
        },
        "homepage": {
          "type": "string",
          "format": "uri",
          "description": "Canonical URL for the tool. Repo, docs site, or vendor page."
        },
        "author": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "name": { "type": "string" },
            "email": { "type": "string", "format": "email" },
            "url": { "type": "string", "format": "uri" }
          }
        },
        "license": {
          "type": "string",
          "description": "SPDX license identifier (e.g., 'MIT', 'Apache-2.0'). Use 'PROPRIETARY' for closed-source paid tools."
        },
        "tags": {
          "type": "array",
          "items": { "type": "string", "pattern": "^[a-z0-9-]+$" },
          "maxItems": 16,
          "description": "Capability tags for discovery. Use the toolspace-controlled vocabulary where possible (email, calendar, storage, ...)."
        }
      }
    },

    "runtime": {
      "type": "object",
      "description": "How the tool is acquired and how the agent invokes it.",
      "required": ["kind", "install"],
      "additionalProperties": false,
      "properties": {
        "kind": {
          "type": "string",
          "enum": ["mcp-stdio", "mcp-http", "python-module", "node-module", "shell-binary", "container"],
          "description": "Execution surface. mcp-stdio is the most common path for an MCP server invoked over stdio. Non-mcp-stdio kinds require actions[] (see top-level allOf)."
        },
        "install": {
          "type": "object",
          "description": "How to acquire the tool's executable artifacts.",
          "required": ["method"],
          "oneOf": [
            {
              "properties": {
                "method": { "const": "pip" },
                "package": { "type": "string", "minLength": 1 },
                "version_spec": { "type": "string", "description": "Pip-style spec, e.g. '==1.2.3' or '>=1.2,<2'." }
              },
              "required": ["method", "package"],
              "additionalProperties": false
            },
            {
              "properties": {
                "method": { "const": "npm" },
                "package": { "type": "string", "minLength": 1 },
                "version_spec": { "type": "string", "description": "npm-style spec, e.g. '^1.2.3'." }
              },
              "required": ["method", "package"],
              "additionalProperties": false
            },
            {
              "properties": {
                "method": { "const": "git" },
                "url": { "type": "string", "format": "uri" },
                "ref": { "type": "string", "description": "Tag, branch, or full commit SHA. Pin to a SHA in production." },
                "subpath": { "type": "string", "description": "Optional path inside the repo." }
              },
              "required": ["method", "url", "ref"],
              "additionalProperties": false
            },
            {
              "properties": {
                "method": { "const": "container" },
                "image": { "type": "string", "description": "OCI image reference, including digest. Example: 'ghcr.io/x/y@sha256:...'." }
              },
              "required": ["method", "image"],
              "additionalProperties": false
            },
            {
              "properties": {
                "method": { "const": "url" },
                "url": { "type": "string", "format": "uri", "description": "Direct download URL for a single executable artifact." },
                "sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$", "description": "Required SHA-256 for integrity check." }
              },
              "required": ["method", "url", "sha256"],
              "additionalProperties": false
            }
          ]
        },
        "entrypoint": {
          "type": "object",
          "description": "How to start the tool after install. For mcp-stdio kinds, this is the MCP server command. For action-based kinds (python-module, shell-binary, container), this is the argv prefix that actions[].invocation.argv_template extends.",
          "required": ["command"],
          "additionalProperties": false,
          "properties": {
            "command": {
              "type": "array",
              "items": { "type": "string" },
              "minItems": 1,
              "description": "argv as an array; first element is the executable. Example: ['python', '-m', 'gmail_tool.server']."
            },
            "cwd": { "type": "string", "description": "Optional working directory." }
          }
        },
        "endpoint_url": {
          "type": "string",
          "format": "uri",
          "description": "For mcp-http / network-based runtimes. Mutually exclusive with entrypoint. For http-kind actions, this is the base URL the action's `path` is appended to."
        }
      }
    },

    "env": {
      "type": "array",
      "description": "Environment variables the agent must collect from the human owner before the tool can run. The order is the order the agent will prompt.",
      "maxItems": 32,
      "items": {
        "type": "object",
        "required": ["name", "prompt", "secret"],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^[A-Z][A-Z0-9_]*$",
            "description": "Environment variable name. SCREAMING_SNAKE_CASE."
          },
          "prompt": {
            "type": "string",
            "minLength": 1,
            "maxLength": 800,
            "description": "Human-readable prompt the agent will display to its owner. Should explain what the value is for and where to obtain it. v0.2 raised the cap from v0.1's 280 to accommodate multi-step OAuth obtain procedures; agents needing a one-line summary should synthesize it from the prompt's first sentence."
          },
          "secret": {
            "type": "boolean",
            "description": "If true, the agent must not log the value, must store it via the host's secret backend, and must never include it in completion contexts. If false, value is config and can be logged."
          },
          "required": {
            "type": "boolean",
            "default": true,
            "description": "If false, the user may skip; the tool must still function (with reduced capability) when absent."
          },
          "validation_regex": {
            "type": "string",
            "description": "Optional ECMAScript regex the value must match. Allows the agent to retry-prompt on bad input."
          },
          "default": {
            "type": "string",
            "description": "Default value if user accepts. NEVER use for secrets."
          },
          "obtain_url": {
            "type": "string",
            "format": "uri",
            "description": "Optional URL the agent can offer the user (e.g., 'create an API key here'). Should require minimum scopes."
          }
        }
      }
    },

    "scopes": {
      "type": "array",
      "description": "Permission declarations. Each entry describes one capability the tool will exercise on the user's behalf. The agent must surface this list to the human owner before install completes.",
      "maxItems": 32,
      "items": {
        "type": "object",
        "required": ["resource", "actions", "rationale"],
        "additionalProperties": false,
        "properties": {
          "resource": {
            "type": "string",
            "description": "What the tool accesses. Vendor-neutral string. Examples: 'gmail.messages', 'fs.local', 'net.outbound', 'stripe.charges'. Referenced by actions[].scopes_used."
          },
          "actions": {
            "type": "array",
            "minItems": 1,
            "items": { "type": "string", "enum": ["read", "write", "delete", "send", "execute", "admin"] },
            "description": "Action verbs the tool will perform on the resource. NOTE: this is the v0.1 verb-list 'actions' field. Distinct from the top-level actions[] catalog added in v0.2; the clash is intentionally preserved for v0.1 compatibility."
          },
          "rationale": {
            "type": "string",
            "minLength": 1,
            "maxLength": 280,
            "description": "Why the tool needs this. Shown to the human; agents may use it for policy reasoning."
          },
          "provider_scope": {
            "type": "string",
            "description": "Optional vendor-specific scope identifier (e.g., a Google OAuth scope URL). Lets the agent verify alignment with credential grants."
          }
        }
      }
    },

    "actions": {
      "type": "array",
      "description": "Catalog of operations the tool exposes to agents. Required when runtime.kind is one of {python-module, node-module, shell-binary, container, mcp-http} (enforced by top-level allOf). Optional but recommended for mcp-stdio (where the MCP protocol provides discovery).",
      "maxItems": 64,
      "items": {
        "type": "object",
        "required": ["name", "summary", "invocation", "side_effects"],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^[a-z][a-z0-9_]{0,62}$",
            "description": "Stable identifier. Lowercase snake_case. Used by the agent to address this operation and by smoke.kind='action-call' to reference it."
          },
          "summary": {
            "type": "string",
            "minLength": 1,
            "maxLength": 280,
            "description": "One-sentence description of what this action does. Surfaced in agent UIs."
          },
          "description": {
            "type": "string",
            "maxLength": 4000,
            "description": "Optional longer description. Markdown allowed. Unlike tool.description, agents MAY parse this — but the structured fields below are the source of truth for behavior."
          },
          "invocation": {
            "type": "object",
            "description": "How the agent calls this action. Discriminated by `kind`. Tokens of the form ${input.<path>} are substituted from validated input; ${env.<NAME>} from collected env vars. Secrets MUST NOT be templated into argv (process listings) — only into stdin-json bodies, http bodies, or http headers.",
            "oneOf": [
              {
                "properties": {
                  "kind": { "const": "subcommand" },
                  "argv_template": {
                    "type": "array",
                    "items": { "type": "string" },
                    "minItems": 1,
                    "description": "Argv appended to runtime.entrypoint.command. May contain ${input.foo} or ${env.FOO} tokens. Example: ['send', '--to', '${input.to}', '--subject', '${input.subject}']."
                  }
                },
                "required": ["kind", "argv_template"],
                "additionalProperties": false
              },
              {
                "properties": {
                  "kind": { "const": "stdin-json" },
                  "argv_template": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Optional argv prefix appended to entrypoint. Validated input is sent as a single JSON object on stdin. Use this kind for actions whose input may be large or contain characters that complicate shell quoting."
                  }
                },
                "required": ["kind"],
                "additionalProperties": false
              },
              {
                "properties": {
                  "kind": { "const": "http" },
                  "method": { "type": "string", "enum": ["GET", "POST", "PUT", "PATCH", "DELETE"] },
                  "path": {
                    "type": "string",
                    "description": "Path appended to runtime.endpoint_url. May contain ${input.foo} tokens for path parameters. Body (when method allows) is JSON of validated input minus path-substituted fields."
                  },
                  "headers": {
                    "type": "object",
                    "additionalProperties": { "type": "string" },
                    "description": "Optional static or templated headers. Header values may reference ${env.FOO} (auth headers) or ${input.foo}."
                  }
                },
                "required": ["kind", "method", "path"],
                "additionalProperties": false
              },
              {
                "properties": {
                  "kind": { "const": "mcp-tool" },
                  "tool_name": { "type": "string", "description": "Name of an MCP tool exposed by this server. Required when an mcp-* runtime opts to enumerate operations explicitly rather than relying on protocol discovery." }
                },
                "required": ["kind", "tool_name"],
                "additionalProperties": false
              }
            ]
          },
          "input": {
            "type": "object",
            "description": "JSON Schema (Draft 2020-12) for the action's input. Agents validate before invocation. Omit for actions that take no input. Validation must reject unknown properties unless the schema explicitly permits them."
          },
          "output": {
            "type": "object",
            "additionalProperties": false,
            "required": ["format"],
            "properties": {
              "format": {
                "type": "string",
                "enum": ["json", "text", "binary", "ndjson-stream", "none"],
                "description": "Wire format for the action's response. 'ndjson-stream' is one JSON object per line on stdout. 'binary' is supported for honesty but tool authors are encouraged to return JSON containing a path/URL instead."
              },
              "schema": {
                "type": "object",
                "description": "JSON Schema (Draft 2020-12) for the output. Recommended when format is 'json' or 'ndjson-stream' (per-record schema)."
              }
            }
          },
          "side_effects": {
            "type": "string",
            "enum": ["none", "read", "write", "destructive"],
            "description": "Severity. 'none' = pure compute. 'read' = no state change but accesses user data (privacy-relevant). 'write' = creates or modifies state recoverably. 'destructive' = sends, deletes, or otherwise cannot-undo. Agents SHOULD require human confirmation for 'destructive' unless an agent-level policy explicitly grants autonomous destructive operations."
          },
          "idempotent": {
            "type": "boolean",
            "default": false,
            "description": "If true, repeated calls with the same input have the same effect as a single call. Lets agents safely retry on transient failure. Default false — the safer assumption when in doubt is 'do not retry.'"
          },
          "scopes_used": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Subset of top-level scopes[].resource that THIS action exercises. Lets agents reason about least-privilege per call. Validators should check every entry resolves to a declared scope (warning, not error, in v0.2)."
          },
          "error_envelope": {
            "type": "string",
            "enum": ["standard", "raw"],
            "default": "raw",
            "description": "If 'standard', the action returns errors in the shape {\"error\": {\"code\": string, \"message\": string, \"details\"?: object}} on failure. If 'raw' (default), the tool's native error shape is opaque to the agent. Default 'raw' so v0.2 imposes no refactor on existing tools; new tools should prefer 'standard'."
          },
          "examples": {
            "type": "array",
            "maxItems": 4,
            "items": {
              "type": "object",
              "required": ["description"],
              "additionalProperties": false,
              "properties": {
                "description": { "type": "string", "maxLength": 280 },
                "input": {},
                "output": {}
              }
            }
          }
        }
      }
    },

    "smoke": {
      "type": "object",
      "description": "How the agent verifies the tool is installed correctly. Run after env collection and before reporting install success. Required.",
      "required": ["kind", "success"],
      "oneOf": [
        {
          "properties": {
            "kind": { "const": "shell" },
            "command": {
              "type": "array",
              "items": { "type": "string" },
              "minItems": 1,
              "description": "argv to execute. Tool's installed env vars are available."
            },
            "timeout_seconds": { "type": "integer", "minimum": 1, "maximum": 300, "default": 30 },
            "success": { "$ref": "#/$defs/smoke_success" }
          },
          "required": ["kind", "command", "success"],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": { "const": "http" },
            "method": { "type": "string", "enum": ["GET", "POST"], "default": "GET" },
            "url": { "type": "string", "format": "uri" },
            "headers": {
              "type": "object",
              "additionalProperties": { "type": "string" },
              "description": "Header values may reference env vars as ${VAR_NAME} (v0.1 syntax preserved for back-compat)."
            },
            "body": { "type": "string", "description": "Optional request body. May reference env vars as ${VAR_NAME}." },
            "timeout_seconds": { "type": "integer", "minimum": 1, "maximum": 300, "default": 30 },
            "success": { "$ref": "#/$defs/smoke_success" }
          },
          "required": ["kind", "url", "success"],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": { "const": "mcp-tool-call" },
            "tool_name": { "type": "string", "description": "Name of an MCP tool exposed by this server to invoke." },
            "arguments": { "type": "object", "description": "JSON arguments for the call. Should be a no-op or read-only operation." },
            "timeout_seconds": { "type": "integer", "minimum": 1, "maximum": 300, "default": 30 },
            "success": { "$ref": "#/$defs/smoke_success" }
          },
          "required": ["kind", "tool_name", "success"],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": { "const": "action-call" },
            "action": {
              "type": "string",
              "pattern": "^[a-z][a-z0-9_]{0,62}$",
              "description": "Name of an action in the manifest's actions[] list. Validators should check the reference resolves and that the referenced action's side_effects is 'none' or 'read'."
            },
            "arguments": { "type": "object", "description": "JSON arguments matching the action's input schema. Should produce a no-op or read-only call." },
            "timeout_seconds": { "type": "integer", "minimum": 1, "maximum": 300, "default": 30 },
            "success": { "$ref": "#/$defs/smoke_success" }
          },
          "required": ["kind", "action", "success"],
          "additionalProperties": false
        }
      ]
    },

    "kill_switch": {
      "type": "object",
      "description": "How the human owner (or the agent itself, on policy violation) revokes the tool. Required. Must be authenticated against the same credentials supplied at install.",
      "required": ["kind"],
      "oneOf": [
        {
          "properties": {
            "kind": { "const": "url" },
            "url": { "type": "string", "format": "uri", "description": "DELETE this URL to revoke. Must accept the install's credentials." }
          },
          "required": ["kind", "url"],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": { "const": "shell" },
            "command": {
              "type": "array",
              "items": { "type": "string" },
              "minItems": 1,
              "description": "argv to execute for revocation. Must be idempotent."
            }
          },
          "required": ["kind", "command"],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": { "const": "manual" },
            "instructions_url": { "type": "string", "format": "uri", "description": "URL with instructions for human revocation. Use only when no programmatic path exists; agents will surface this to humans verbatim." }
          },
          "required": ["kind", "instructions_url"],
          "additionalProperties": false
        }
      ]
    },

    "cost": {
      "type": "object",
      "description": "Billing model surfaced to the agent and human owner. All values in USD cents.",
      "additionalProperties": false,
      "properties": {
        "install_fee_cents": {
          "type": "integer",
          "minimum": 0,
          "description": "One-time install fee. 0 for free tools."
        },
        "monthly_fee_cents": {
          "type": "integer",
          "minimum": 0,
          "description": "Recurring monthly fee. 0 if usage-based or free."
        },
        "usage_model": {
          "type": "string",
          "enum": ["none", "per-call", "per-token", "external"],
          "description": "'external' means the tool charges via its own credentials (e.g., user's own API key); the marketplace is not in the payment loop."
        },
        "estimate_url": {
          "type": "string",
          "format": "uri",
          "description": "Optional URL returning a JSON {monthly_estimate_cents, basis} object. Lets agents budget."
        }
      }
    },

    "support": {
      "type": "object",
      "description": "Where to file bugs, get help, or report security issues.",
      "additionalProperties": false,
      "properties": {
        "issues_url": { "type": "string", "format": "uri" },
        "security_email": { "type": "string", "format": "email" },
        "docs_url": { "type": "string", "format": "uri" }
      }
    }
  },

  "allOf": [
    {
      "title": "actions[] required when runtime.kind is not self-describing",
      "description": "python-module, node-module, shell-binary, container, and mcp-http kinds give the agent no protocol-level way to discover operations. They MUST publish an actions[] catalog. mcp-stdio is exempt because MCP discovery covers it.",
      "if": {
        "properties": {
          "runtime": {
            "type": "object",
            "required": ["kind"],
            "properties": {
              "kind": { "enum": ["python-module", "node-module", "shell-binary", "container", "mcp-http"] }
            }
          }
        },
        "required": ["runtime"]
      },
      "then": {
        "required": ["actions"],
        "properties": {
          "actions": { "minItems": 1 }
        }
      }
    }
  ],

  "$defs": {
    "smoke_success": {
      "type": "object",
      "description": "Conditions that, if all met, indicate a successful smoke run. Logical AND across present fields.",
      "additionalProperties": false,
      "properties": {
        "exit_code": {
          "type": "integer",
          "description": "Required exit code (shell smoke). Defaults to 0 if absent."
        },
        "http_status": {
          "type": "integer",
          "description": "Required HTTP status (http smoke)."
        },
        "stdout_regex": {
          "type": "string",
          "description": "stdout must match this ECMAScript regex (shell smoke)."
        },
        "body_regex": {
          "type": "string",
          "description": "Response body must match this regex (http smoke)."
        },
        "json_pointer_equals": {
          "type": "object",
          "description": "Map of JSON Pointer (RFC 6901) -> required value. Applies to JSON responses, mcp-tool-call results, and action-call results.",
          "additionalProperties": true
        },
        "no_error_field": {
          "type": "boolean",
          "description": "If true, JSON response/result must not contain a top-level 'error' field. Useful for mcp-tool-call and action-call."
        }
      }
    }
  }
}
