CrawlManifestMCP endpoint
Point it at a website. Get back tools that return something.
ClawHarbor crawls a public site, reads its palette and the things a customer can actually do there, and compiles both into one versioned manifest. That manifest is served two ways: a hosted MCP endpoint, and a branded page an agent can drive. Every tool is executed against the live site before it ships, and the ones that come back empty are dropped.
Read-only · 1 request per second · robots.txt honoured · never behind a login
9 pages, at 0.5 requests per second, with
robots.txt fetched first. Nothing is rehosted —
the manifest is 26.8 kB of shape, not content.
mcp.clawharbor.io/t/kino-vela/mcp
Remote MCP. 4 tools, 4 predeclared ui:// resources.
WebMCP page. The same tools on document.modelContext.
- list_screenings
- get_film_details
- get_ticket_prices
- get_cinema_info
and the rows
It answers with rows, not prose
The endpoint below is running right now. This exchange was made against it on
2026-08-28 — the prices are read out of
kinovela.clawharbor.io/cijene at call time, not out of a cache we built
at crawl time.
POST /t/kino-vela/mcp HTTP/1.1
Host: mcp.clawharbor.io
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_ticket_prices
{"jsonrpc":"2.0","id":2,"method":"tools/call",
"params":{"name":"get_ticket_prices","arguments":{}}}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "complete",
"content": [
{ "type": "text",
"text": "4 results — Cijene at Kino Vela. Live as of 28 Aug, 05:14. Complete on kinovela.clawharbor.io." }
],
"structuredContent": {
"ok": true,
"view": "cijene",
"count": 4,
"freshness": { "state": "live", "asOf": "2026-08-28T05:14:05.286Z",
"origin": "https://kinovela.clawharbor.io" },
"items": [
{ "name": "Studenti i umirovljenici", "price": "3,50 €", "price_2": "Uz ispravu" },
{ "name": "Ponoćni program", "price": "4,00 €", "price_2": "Petak i subota, 23:30" },
{ "name": "Godišnja članska", "price": "40,00 €", "price_2": "Sve projekcije, cijela godina" },
{ "name": "Redovna", "price": "5,00 €", "price_2": "Svaka projekcija" }
],
"deepLink": "https://kinovela.clawharbor.io"
}
}
}
Abridged in two places for width: the duplicate summary string and the per-row url: null field are removed. Everything else is byte-for-byte what the server returned. Note resultType: "complete", which MCP core 2026-07-28 requires on every result, and freshness, which every ClawHarbor result carries so an assistant can say out loud when it is reading something stale.
declared
Four tools, generated, not hand-written
Nobody wrote these. The crawler read a cinema's HTML, proposed them, executed each
one against the origin, and kept the four that came back with rows. Each tool
descriptor carries _meta.ui.resourceUri, so a chat client that supports
MCP Apps renders a branded widget instead of a wall of JSON.
List the weekly screening schedule
Get details and showtimes for one film
Get ticket prices and concessions
Get cinema location and contact details
Read the whole thing at tenants/kino-vela/manifest.json — 26.8 kB covering brand tokens, a domain model lifted from the site's schema.org blocks, four declarative bindings and a content hash. A binding is data, never code: nine operations, no expressions, nothing evaluable. Manifests are generated from other people's websites and served multi-tenant, so an evaluable binding would be a remote-code-execution hole with a nice name.
and refusal
We publish the number of tools we threw away
Every proposed capability is executed against the live origin before it ships. If the binding returns nothing, the tool does not exist. That is the whole differentiator, and the only honest way to advertise it is to show what it costs. So here is a real run across 51 businesses we do not own, with no model in the loop.
Capabilities the crawler proposed across 51 sites.
Executed against the origin and returned nothing. Dropped before reaching a manifest.
Executed and returned rows. This is what the probe gate is for.
Scored usable by an offline rubric: the name, the field names, the description and the customer question all have to hold.
The gap between 128 and 5 is the finding. "Returned rows" and "returned
something meaningful" are different questions, and that run only asked the first
one. The clean demonstration in the report is woxadria.hr: the
extractor found the real price list and returned
{"flex":"Vanjsko pranje","grid":"Mali auto15 EUR…","font_bold":"15 EUR"}.
The data is perfect. Every key is a Tailwind class.
A second gate now exists for exactly that failure —
apps/crawler/src/quality.ts rejects a proposal whose output fields are
markup rather than meaning — but it was built after the run above, so
none of these four numbers reflect it. We are not going to reprint the table with a
better number until there is a run that earned one. The report's own estimate of the
ceiling if naming alone were fixed is 19/51 sites.
Read it in full in the repository. It is written to be uncomfortable: per-vertical breakdowns, the reason each dropped proposal was dropped, and the cases where brand extraction confidently returned a colour off a Cloudflare block page.
Reproduce: node evals/prescreen.mjs → run-eval.mjs → score.mjs → report.mjs. No API credit was spent producing any number on this page.
one manifest
The same tools, shaped by where they land
One manifest feeds both surfaces, but they are not interchangeable, and the reason is in the specifications rather than in our roadmap.
A branded page an agent drives
Hosted in the site's own palette and type. It registers the tools on
document.modelContext, a browser agent calls them, and the page
repaints in front of the person watching. No extension, no SDK, no plugin.
Annotations are exactly readOnlyHint and
untrustedContentHint; teardown is an AbortSignal,
because there is no unregisterTool.
A remote MCP server with widgets
Predeclared ui:// resources at mimeType
text/html;profile=mcp-app. The tool descriptor carries
_meta.ui.resourceUri; the result carries no UI reference at all.
Paste one URL into a client that speaks MCP and it is connected. Results are read-only and carry a deep link back to the operator's own site.
mcp.clawharbor.io/t/kino-vela/mcpWhy they differ. A WebMCP tool result cannot carry UI. That is not a gap we are working around — it is the spec's stated design intent, so that a site does not get disintermediated by the agent reading it. The page is the result, which is why every WebMCP tool writes to the DOM before it returns. MCP Apps made the opposite call: the UI is a resource, predeclared at connect time and addressed from the descriptor, so a chat client can render it inline. One manifest can serve both. One renderer cannot.
at the origin
What the crawler does to someone else’s server
A product that reads other people's websites for a living is one badly-behaved crawler away from being a nuisance. The rules are fixed in the code, not in a policy document nobody reads.
ClawHarborBot/1.0 (+https://clawharbor.io/bot)
One request per second, ceiling. Serialised per origin — never parallel, never bursty.
Fetched before anything else and re-checked on every crawl. Disallow is final.
Behind a login. No tool writes, books, pays or authenticates — a checkout returns a deep link a human has to confirm.
A manifest of shape: colours, type scale, selectors, tool descriptions. No content, no images, no font files, no history.
On request from the domain owner, without review or argument. How to file.
There are 15 crawled third-party manifests in this repository and not one of them is published. They sit in drafts/ because a manifest describes somebody else's brand and we have no standing to serve it. The only tenant that is live is Kino Vela, a cinema we own outright, which is why the demo exists at all.
Point it at your own site and see what clears.
The console runs the crawl in front of you, prints what was proposed, what was executed, what returned rows and what was turned back. If the answer is nothing, it says nothing.