harbor_delete_artifact
Permanently delete an artifact from a Harbor registry by specifying its tag or digest. The manifest is removed immediately and blobs are reclaimed by garbage collection.
Instructions
Delete a single artifact by tag or digest.
DESTRUCTIVE & IRREVERSIBLE — Harbor immediately removes the manifest from its catalogue; the underlying blobs are reclaimed by the next GC sweep. There is no soft-delete or undo.
Returns the freed space and tag list for confirmation.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| project_name | Yes | Harbor project name. | |
| repository_name | Yes | Repository name within the project. | |
| reference | Yes | Tag (e.g. 'v1.0') or digest (e.g. 'sha256:...'). |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| success | Yes | ||
| project | Yes | ||
| repository | Yes | ||
| reference | Yes | ||
| deleted_tags | Yes | ||
| freed_size | Yes | ||
| freed_bytes | Yes | ||
| error | Yes |
Implementation Reference
- src/harbor_registry_mcp/tools.py:512-576 (handler)The harbor_delete_artifact function is the actual tool handler. It resolves the artifact by digest or tag via _list_artifacts, then calls client.delete() to remove it from Harbor. Returns a DeleteArtifactOutput with freed size and tags.
def harbor_delete_artifact( project_name: Annotated[str, Field(min_length=1, max_length=255, description="Harbor project name.")], repository_name: Annotated[ str, Field(min_length=1, max_length=255, description="Repository name within the project.") ], reference: Annotated[ str, Field(min_length=1, max_length=255, description="Tag (e.g. 'v1.0') or digest (e.g. 'sha256:...').") ], ) -> DeleteArtifactOutput: """Delete a single artifact by tag or digest. **DESTRUCTIVE & IRREVERSIBLE** — Harbor immediately removes the manifest from its catalogue; the underlying blobs are reclaimed by the next GC sweep. There is no soft-delete or undo. Returns the freed space and tag list for confirmation. """ try: client = get_client() # Resolve the artifact first so we can return helpful metadata. artifacts = _list_artifacts(client, project_name, repository_name) target = None for art in artifacts: tags = [t["name"] for t in (art.get("tags") or [])] if art.get("digest") == reference or reference in tags: target = art break if target is None: empty: DeleteArtifactOutput = { "success": False, "project": project_name, "repository": repository_name, "reference": reference, "deleted_tags": [], "freed_size": "0 B", "freed_bytes": 0, "error": f"Artifact {reference!r} not found in {project_name}/{repository_name}", } md = f"❌ Artifact {reference!r} not found in {project_name}/{repository_name}" return output.ok(empty, md) # type: ignore[return-value] client.delete( f"/projects/{project_name}/repositories/{encode_repo(repository_name)}/artifacts/{encode_repo(reference)}" ) size = int(target.get("size", 0) or 0) tags = [t["name"] for t in (target.get("tags") or [])] result: DeleteArtifactOutput = { "success": True, "project": project_name, "repository": repository_name, "reference": reference, "deleted_tags": tags, "freed_size": size_human(size), "freed_bytes": size, "error": None, } md = ( f"✅ Deleted {project_name}/{repository_name}@{reference} " f"(tags: {','.join(tags) or '(untagged)'}, freed {size_human(size)})" ) return output.ok(result, md) # type: ignore[return-value] except Exception as exc: output.fail(exc, f"deleting artifact {reference} in {project_name}/{repository_name}") - src/harbor_registry_mcp/tools.py:501-510 (registration)The @mcp.tool decorator registers the function as a FastMCP tool named 'harbor_delete_artifact' with annotations marking it as destructive, non-idempotent, etc.
@mcp.tool( name="harbor_delete_artifact", annotations={ "title": "Delete Single Artifact", "readOnlyHint": False, "destructiveHint": True, "idempotentHint": False, "openWorldHint": True, }, structured_output=True, - DeleteArtifactOutput TypedDict defines the return schema: success, project, repository, reference, deleted_tags, freed_size, freed_bytes, error.
class DeleteArtifactOutput(TypedDict): """Result of :func:`harbor_delete_artifact`. ``success`` is ``False`` when the artifact was not found before delete; ``error`` carries an explanation in that case, else is ``None``. """ success: bool project: str repository: str reference: str deleted_tags: list[str] freed_size: str freed_bytes: int error: str | None - _list_artifacts helper fetches all artifact pages for a given repo, used by the handler to find the artifact by digest or tag.
def _list_artifacts(client: Any, project_name: str, repository_name: str) -> list[dict[str, Any]]: """Fetch every artifact for a repository across all pages.""" return client.get_all_pages( f"/projects/{project_name}/repositories/{encode_repo(repository_name)}/artifacts", page_size=100, extra_params={"with_tag": True, "with_scan_overview": True}, ) - encode_repo URL-encodes repository names (replacing / with %2F) for the Harbor REST API call in the delete path.
def encode_repo(repository_name: str) -> str: """URL-encode a Harbor repository name. Harbor repos can contain slashes (e.g. ``nginx-proxy/nginx``); its REST API expects them percent-encoded as ``%2F`` inside the path. """ return repository_name.replace("/", "%2F")