feat(standards): add Drone CI standard (PyPI package + Docker deployment)
Document the two `.drone.yml` shapes used across the org, derived from the repos: - Type A `python-lib.yaml`: build-and-publish-pypi + backfill-pypi-from-tags - Type B `service-docker.yaml`: build-and-deploy-service + build-and-deploy-site - add reference page `10_drone_pipelines` - vendor `src/templates/drone/` into the MCP bundle (`drone.json`) - expose `registry://drone` + `list_drone_templates` / `get_drone_template` - wire `build.py` to vendor drone templates; rebuild nav/index (14 modules)
This commit is contained in:
73
registry.py
73
registry.py
@@ -10,6 +10,7 @@ Resources:
|
||||
docs://spec (the pinned auth-server OpenAPI contract)
|
||||
registry://libraries (the structured catalog)
|
||||
registry://components (the template component index)
|
||||
registry://drone (the CI template index)
|
||||
|
||||
Tools:
|
||||
ping (from the base server)
|
||||
@@ -17,6 +18,8 @@ Tools:
|
||||
list_app_components (the template component kinds)
|
||||
get_app_component (kind -> exact template file, seed tokens renamed)
|
||||
get_app_template (the whole app skeleton as a path -> content map)
|
||||
list_drone_templates (the two CI shapes: PyPI package / Docker deployment)
|
||||
get_drone_template (kind -> exact .drone.yml template)
|
||||
|
||||
Prompts:
|
||||
intake (the questions to gather before building)
|
||||
@@ -126,6 +129,32 @@ class StandardsMCPServer(MCPServer):
|
||||
return {"error": "not_found", "path": str(path)}
|
||||
return json.loads(path.read_bytes())
|
||||
|
||||
def _drone(self) -> dict[str, Any]:
|
||||
"""Load the CI (Drone) template manifest."""
|
||||
path = Path(self.mcp_root) / "drone.json"
|
||||
if not path.exists():
|
||||
return {"error": "not_found", "path": str(path), "components": []}
|
||||
return json.loads(path.read_bytes())
|
||||
|
||||
def _find_drone_component(self, kind: str) -> dict[str, Any] | None:
|
||||
for entry in self._drone().get("components", []):
|
||||
if entry.get("kind") == kind:
|
||||
return entry
|
||||
return None
|
||||
|
||||
def _read_drone_component(self, entry: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Read a vendored drone template file."""
|
||||
rel = entry["path"]
|
||||
path = Path(self.mcp_root) / "templates" / "drone" / rel
|
||||
if not path.exists():
|
||||
return {"error": "not_found", "path": str(path)}
|
||||
return {
|
||||
"kind": entry["kind"],
|
||||
"path": rel,
|
||||
"placeholder": entry.get("placeholder"),
|
||||
"content": path.read_text(encoding="utf-8"),
|
||||
}
|
||||
|
||||
def _find_component(self, kind: str) -> dict[str, Any] | None:
|
||||
for entry in self._components().get("components", []):
|
||||
if entry.get("kind") == kind:
|
||||
@@ -195,6 +224,10 @@ class StandardsMCPServer(MCPServer):
|
||||
def components() -> dict[str, Any]:
|
||||
return self._components()
|
||||
|
||||
@self.app.resource("registry://drone")
|
||||
def drone() -> dict[str, Any]:
|
||||
return self._drone()
|
||||
|
||||
@self.app.resource("docs://spec")
|
||||
def spec() -> dict[str, Any]:
|
||||
"""Return the pinned auth-server OpenAPI contract.
|
||||
@@ -307,6 +340,46 @@ class StandardsMCPServer(MCPServer):
|
||||
"files": files,
|
||||
}
|
||||
|
||||
@self.app.tool()
|
||||
def list_drone_templates() -> list[dict[str, Any]]:
|
||||
"""List the CI (Drone) template shapes.
|
||||
|
||||
There are exactly two types of ``.drone.yml`` in the org: a Python
|
||||
package published to the private PyPI index, and a Docker
|
||||
deployment that builds, pushes, and runs a container.
|
||||
|
||||
Returns:
|
||||
list[dict[str, Any]]:
|
||||
Entries with ``kind``, ``path``, ``placeholder`` and
|
||||
``description``. Fetch one with :func:`get_drone_template`.
|
||||
"""
|
||||
return self._drone().get("components", [])
|
||||
|
||||
@self.app.tool()
|
||||
def get_drone_template(kind: str) -> dict[str, Any]:
|
||||
"""Return the exact CI template for a component kind.
|
||||
|
||||
Use this to copy the current ``.drone.yml`` shape instead of
|
||||
inventing one. See :func:`list_drone_templates` for valid kinds
|
||||
("drone-pypi", "drone-service").
|
||||
|
||||
Args:
|
||||
kind (str): Component kind, e.g. "drone-pypi" or
|
||||
"drone-service".
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: ``{kind, path, placeholder, content}`` or an
|
||||
error entry when the kind is unknown.
|
||||
"""
|
||||
entry = self._find_drone_component(kind)
|
||||
if entry is None:
|
||||
return {
|
||||
"error": "unknown_kind",
|
||||
"kind": kind,
|
||||
"available": [c["kind"] for c in self._drone().get("components", [])],
|
||||
}
|
||||
return self._read_drone_component(entry)
|
||||
|
||||
@self.app.prompt()
|
||||
def intake() -> str:
|
||||
"""Return the questions to gather before building an app.
|
||||
|
||||
Reference in New Issue
Block a user