{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://behavioralstate.io/v1/schemas/discovery.json",
  "title": "BSP Discovery Manifest",
  "description": "Schema for the /.well-known/bsp discovery manifest. Every BSP-compliant endpoint must expose this at GET /.well-known/bsp.",
  "type": "object",
  "required": ["BSP"],
  "properties": {
    "BSP": {
      "type": "object",
      "required": ["version", "services", "capabilities"],
      "properties": {
        "version": {
          "type": "string",
          "description": "BSP spec version (semver: MAJOR.MINOR.PATCH)",
          "pattern": "^\\d+\\.\\d+\\.\\d+$",
          "examples": ["0.4.0"]
        },
        "services": {
          "type": "object",
          "description": "Service definitions with transport bindings. Keys are fully qualified service names (e.g. io.bsp.agents).",
          "additionalProperties": {
            "$ref": "#/$defs/service"
          }
        },
        "capabilities": {
          "type": "array",
          "description": "Supported capabilities with schema URLs.",
          "items": {
            "$ref": "#/$defs/capability"
          }
        },
        "agents": {
          "type": "array",
          "description": "Snapshot of known agents at manifest-build time (optional). This is a discovery hint only — it is not the authoritative agent list. For the live list, consumers must call GET /agents on the REST endpoint. For dynamic systems where agents are created at runtime, this array may be omitted or contain only representative examples.",
          "items": {
            "$ref": "agents/registry.json#/$defs/agentDescriptor"
          }
        },
        "authentication": {
          "$ref": "#/$defs/authentication"
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false,
  "$defs": {
    "service": {
      "type": "object",
      "description": "A top-level service domain the runtime supports.",
      "required": ["version", "description"],
      "properties": {
        "version": {
          "type": "string",
          "description": "Service version (semver).",
          "pattern": "^\\d+\\.\\d+\\.\\d+$"
        },
        "description": {
          "type": "string",
          "description": "Human-readable description of the service."
        },
        "spec": {
          "type": "string",
          "format": "uri",
          "description": "URL to the service specification."
        },
        "http": { "$ref": "#/$defs/httpTransport"
        },
        "mcp": {
          "$ref": "#/$defs/mcpTransport"
        },
        "a2a": {
          "$ref": "#/$defs/a2aTransport"
        },
        "grpc": {
          "$ref": "#/$defs/grpcTransport"
        }
      },
      "additionalProperties": false
    },
    "capability": {
      "type": "object",
      "description": "A composable capability within a service.",
      "required": ["name", "version", "description", "spec", "schema"],
      "properties": {
        "name": {
          "type": "string",
          "description": "Fully qualified capability name using reverse-domain notation. BSP built-in capabilities use the io.bsp.* prefix (e.g. io.bsp.agents.registry). Custom or domain-specific capabilities must use a unique reverse-domain prefix owned by the implementer (e.g. io.dotquant.trading, com.acme.inventory).",
          "pattern": "^[a-z][a-z0-9]*(\\.[a-z][a-z0-9_-]*)*$"
        },
        "version": {
          "type": "string",
          "description": "Capability version (semver).",
          "pattern": "^\\d+\\.\\d+\\.\\d+$"
        },
        "description": {
          "type": "string",
          "description": "Human-readable description."
        },
        "spec": {
          "type": "string",
          "format": "uri",
          "description": "URL to the capability specification."
        },
        "schema": {
          "type": "string",
          "format": "uri",
          "description": "URL to the JSON Schema for this capability."
        },
        "extends": {
          "type": "string",
          "description": "Parent capability this extends (if any)."
        },
        "service": {
          "type": "string",
          "description": "Key of the service (in the manifest's services object) that implements this capability. Required when the capability name prefix does not match the service name — for example, when a custom service (io.dotquant.trading) implements a standard BSP capability (io.bsp.agents.commands). Consumers use this field to resolve which http.endpoint to call for the capability's endpoints. Omit only when the capability name shares its prefix with exactly one service key (e.g. io.bsp.agents.* capabilities under the io.bsp.agents service)."
        },
        "status": {
          "type": "string",
          "enum": ["active", "partial", "planned"],
          "description": "Capability availability status. 'active' means fully implemented. 'partial' means a subset of the capability's endpoints or features are implemented — consumers must not assume all required endpoints exist. 'planned' means not yet implemented. Omitted means active."
        },
        "endpoints": {
          "type": "array",
          "description": "Machine-readable list of HTTP endpoints exposed by this capability. Paths are relative to http.endpoint. The HTTP method signals whether each operation is a read (GET) or a write (POST/PUT/PATCH/DELETE). Consumers can use this to discover the catalogue URL, determine mutability, and self-bootstrap without reading the spec.",
          "items": {
            "type": "object",
            "required": ["method", "path"],
            "properties": {
              "method": {
                "type": "string",
                "enum": ["GET", "POST", "PUT", "PATCH", "DELETE"],
                "description": "HTTP method. GET is read-only. POST/PUT/PATCH/DELETE mutate state."
              },
              "path": {
                "type": "string",
                "description": "Path relative to http.endpoint (e.g. /commands, /services/{id})."
              },
              "description": {
                "type": "string",
                "description": "Human-readable description of this endpoint."
              }
            },
            "additionalProperties": false
          }
        }
      },
      "additionalProperties": false
    },
    "httpTransport": {
      "type": "object",
      "required": ["endpoint"],
      "properties": {
        "endpoint": {
          "type": "string",
          "format": "uri",
          "description": "Consumer-facing base URL for all HTTP operations. This must be the public or proxy-facing address reachable by consumers — never an internal backend address. All capability endpoint paths are appended to this URL."
        }
      },
      "additionalProperties": false
    },
    "mcpTransport": {
      "type": "object",
      "required": ["transport", "server"],
      "properties": {
        "transport": {
          "type": "string",
          "enum": ["stdio", "sse", "http"],
          "description": "MCP transport type."
        },
        "server": {
          "type": "string",
          "description": "MCP server identifier or URL."
        },
        "authentication": {
          "$ref": "#/$defs/mcpAuthentication"
        }
      },
      "additionalProperties": false
    },
    "a2aTransport": {
      "type": "object",
      "required": ["agent_card_url"],
      "properties": {
        "agent_card_url": {
          "type": "string",
          "format": "uri",
          "description": "URL to the A2A Agent Card."
        }
      },
      "additionalProperties": false
    },
    "grpcTransport": {
      "type": "object",
      "required": ["endpoint"],
      "properties": {
        "endpoint": {
          "type": "string",
          "description": "gRPC endpoint address."
        },
        "proto": {
          "type": "string",
          "format": "uri",
          "description": "URL to the .proto file."
        }
      },
      "additionalProperties": false
    },
    "authentication": {
      "type": "object",
      "description": "Authentication requirements for accessing this BSP endpoint. Consumers must present credentials according to the declared scheme before calling any protected endpoint.",
      "required": ["type"],
      "properties": {
        "type": {
          "type": "string",
          "enum": ["none", "bearer", "apiKey", "oauth2"],
          "description": "Authentication mechanism. Use 'none' for public endpoints."
        },
        "scheme": {
          "type": "string",
          "description": "For 'bearer': the Authorization header scheme (e.g. 'Bearer'). For 'apiKey': the header or query parameter name."
        },
        "in": {
          "type": "string",
          "enum": ["header", "query"],
          "description": "Where the API key is passed when type is 'apiKey'."
        },
        "scopes": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Required OAuth2 / token scopes (if applicable)."
        },
        "tokenUrl": {
          "type": "string",
          "format": "uri",
          "description": "Token endpoint URL for OAuth2 or token-based flows."
        },
        "docs": {
          "type": "string",
          "format": "uri",
          "description": "URL to human-readable authentication documentation."
        }
      },
      "additionalProperties": false
    },
    "mcpAuthentication": {
      "type": "object",
      "description": "Authentication requirements for connecting to an MCP server. Consumers (including AI agents and IDE tooling) must read this block and supply the declared credentials when connecting.",
      "required": ["type"],
      "properties": {
        "type": {
          "type": "string",
          "enum": ["none", "bearer", "apiKey", "oauth2"],
          "description": "Authentication mechanism."
        },
        "headers": {
          "type": "array",
          "description": "Required HTTP headers for 'apiKey' type. Use when more than one header is needed (e.g. an API key and a tenant ID). For single-header API key auth, a one-entry array is preferred.",
          "items": {
            "$ref": "#/$defs/mcpAuthHeader"
          }
        },
        "scheme": {
          "type": "string",
          "description": "For 'bearer': the Authorization header prefix (e.g. 'Bearer')."
        },
        "tokenUrl": {
          "type": "string",
          "format": "uri",
          "description": "Token endpoint URL for 'oauth2' or token-based flows."
        },
        "scopes": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Required OAuth2 / token scopes (if applicable)."
        },
        "docs": {
          "type": "string",
          "format": "uri",
          "description": "URL to human-readable authentication documentation."
        }
      },
      "additionalProperties": false
    },
    "mcpAuthHeader": {
      "type": "object",
      "description": "Descriptor for a single required HTTP header when connecting to an MCP server.",
      "required": ["name"],
      "properties": {
        "name": {
          "type": "string",
          "description": "HTTP header name (e.g. 'X-Api-Key')."
        },
        "description": {
          "type": "string",
          "description": "Human-readable description of what value to supply."
        },
        "example": {
          "type": "string",
          "description": "Pre-filled example value. Tooling (e.g. VS Code Copilot MCP config generator) may use this to populate the header automatically. A per-tenant manifest may pre-fill this with the resolved tenant ID."
        }
      },
      "additionalProperties": false
    }
  }
}
