Hosted Assets
Publish a page, deck, or report your agent just generated and get a live URL back in the same tool call. No build step, no repo, no deploy — public to anyone with the link, or private behind a signed one you can revoke.
Your agent makes things that need somewhere to live. A one-pager for a client, a slide deck, a report, a standalone interactive page. BlackOps hosted images and blog posts and nothing in between, so the only way to share one of those was to leave the conversation entirely: download the file, open a hosting service, deploy it, come back with a link.
Hosted assets close that gap. The agent hands BlackOps the files, picks public or private, and the live URL comes back in the tool response.
Publishing
The simplest case is one self-contained page:
publish_asset(
title: "Q3 Board Deck",
html: "<!doctype html><html>…</html>",
visibility: "public"
)
The response carries the URL, and it works immediately.
Bundles are first class, because a generated artifact is not reliably a single file. Pass files instead of html, with relative paths:
publish_asset(
title: "Q3 Board Deck",
files: [
{ path: "index.html", content_text: "…" },
{ path: "assets/app.css", content_text: "…" },
{ path: "assets/app.js", content_text: "…" },
{ path: "img/chart.png", content_base64: "…" }
]
)
Relative references inside the bundle resolve against the asset's own URL, so a deck that works locally works published, unmodified. index.html is the entry point by default; if the generator named it something else, either it is the only HTML file in the bundle (in which case BlackOps picks it) or you name it with entry_path.
Where assets live
An asset serves from your site's own domain — your custom domain if you have one, otherwise your blackopscenter.com subdomain:
https://yoursite.com/a/q3-board-deck/
Assets belong to a site, not to your account as a whole. The same slug on two of your sites is two different assets, and an asset is only reachable from the host it belongs to.
The trailing slash matters and is part of the canonical URL. It is what makes a bundle's relative paths resolve correctly.
Public and private
Public assets serve to anyone with the link. They are marked noindex, so they will not turn up in search results or compete with your real pages.
Private assets need a signed share link, which publish_asset returns as share_url. That link needs no login, so you can send it to a client who has no BlackOps account. Without a valid token the URL returns 404, not 403 — a wrong or missing token is indistinguishable from an asset that was never there, so the link itself never confirms anything exists.
Share links do not expire by default. If you want one that does, mint it with get_asset_link and an expires_in_days. Minting a new link never invalidates an old one, which means revoking the asset is the only way to withdraw access.
Republishing
Pass a source_id and republishing becomes idempotent:
publish_asset(source_id: "q3-board-deck", html: "…")
The second call updates the same asset in place — same URL, revision counter bumped, previous files replaced — instead of creating a duplicate. That is what lets an agent publish, look at the result, fix something, and publish again without leaving a trail of dead links behind.
Republishing overwrites. BlackOps does not keep previous versions.
Visibility is inherited on a republish. If an asset is private and you republish without saying otherwise, it stays private — omitting visibility never reverts it to public. Pass an explicit value when you actually want to change it.
Revoking
Revoke an asset from Admin → Hosted assets, or with revoke_asset. The files are deleted and the URL returns 404 on the next request, including for anyone holding a private share link. This cannot be undone.
Revoked assets stay listed in the admin, greyed out, so "why did the link I sent stop working" always has an answer.
Social preview cards
A published asset gets a social preview card automatically, so pasting the link into X, LinkedIn, Slack or iMessage shows an image and a title instead of a bare URL. The first paste already works: a fast, deterministic card renders immediately and comes back in the publish_asset response as og_image_url. A content-aware version — generated from the asset's own title and content, styled with your site's brand colors — replaces it at the same URL moments later, so nothing needs to be re-shared or re-fetched.
Customize it with og_prompt (the visual direction), og_title and og_subtitle (the text on the card), or include_title_text: false for a purely visual card with no text. Pass og: false to skip the card entirely. Private assets never get one — a card is a public, unauthenticated image, and generating one would be a way to preview a private asset's title before anyone has its share link.
Meta tags are injected into the entry document's <head> only where you have not already set them — an og:image (or og:title, og:description, og:url, twitter:card) you authored yourself always wins, and nothing of yours is rewritten.
What the sandbox means for your page
Your markup is stored and served exactly as authored — no sanitization, no rewriting. Inline <style> and <script> are preserved, because a sanitizer that strips a deck's own script ships a blank page. The one exception is <head> metadata: as described above, BlackOps adds the social-preview tags you have not already set, so the link previews without you having to write them yourself.
Every asset is served sandboxed, in an opaque origin. Scripts run and forms submit, so a deck or an interactive report behaves normally. What it cannot do is read cookies or storage belonging to your site or to BlackOps, even though it serves from your domain.
Opening a link in a new tab and offering a file to download both work, because reports and decks legitimately do those things. A window your page opens inherits the same sandbox, so it is no more privileged than the page that opened it.
The practical limit worth knowing: an asset cannot use localStorage or sessionStorage. Attempts to read them throw. A page that needs to persist state across visits is an app, and belongs somewhere with its own origin.
Limits
Assets never expire; they live until you revoke them. Each site can hold up to 200 assets totalling 500 MB, with a 25 MB cap per asset, 10 MB per file, and 200 files per bundle. They have their own quota and do not count against your media library.
Tools
| Tool | What it does |
|---|---|
publish_asset | Publish or republish a page or bundle; returns the live URL |
list_assets | List a site's assets, with URLs, visibility, size, and revision |
get_asset_link | Mint another signed share link for a private asset, optionally expiring |
revoke_asset | Take an asset offline; the URL 404s on the next request |
These are catalog tools. An MCP client reaches them through describe_tools and use_tool rather than finding them on the default tool surface.