url-metadata-api
README.md
# URL Metadata API
Given a URL, returns `title`/`description`/`image`/`site_name` scraped from its OpenGraph/meta tags
(Microlink-style link-preview API). NEXUS candidate #4 -- **manual build, not FORGE-generated.**
- `POST /extract {"url": "..."}` -- charged **$0.01 via x402** (Base Sepolia testnet).
- MCP tool `extract_url_metadata` at `/mcp` -- **currently free**, see "Known limitations".
- `GET /health`, `GET /.well-known/agent-card.json`, `GET /openapi.json` (has `x-payment-info`).
## Deploy target: Cloud Run, not Railway
Built from `products/mcp-boilerplate-gumroad/template/` (same MCP+x402+FastAPI pattern as
`similarity-search-api`/`ws`), adapted for Cloud Run instead of Railway. See
`skills/infra-deploy-ops` for the reusable deploy pipeline (`scripts/deploy_cloud_run.sh` at the repo
root). Two-step deploy (same gotcha the boilerplate README documents):
```bash
# 1. First deploy -- PUBLIC_DOMAIN not known yet, every real request 421s until step 2.
./scripts/deploy_cloud_run.sh url-metadata-api manual_assets/url-metadata-api
# 2. Grab the printed *.run.app URL, then:
gcloud run services update url-metadata-api --region us-central1 --project nexus-505016 \
--update-env-vars PUBLIC_DOMAIN=<the-real-domain>
```
## Known limitations (left unfixed on purpose -- CLAUDE.md SS3, no gate without evidence it's needed)
- **MCP tool calls are not charged.** `PaymentMiddlewareASGI` only gates `POST /extract`; the MCP tool
calls the shared `extract_metadata()` function directly in-process (no HTTP re-entry into the ASGI
app -- that re-entry path is what caused the real "every MCP call gets a spurious 402" bug documented
in `skills/mcp-server-generation`). Adding a real MCP-native x402 gate (`x402[...,mcp]`'s own
payment-gate mechanism, same as what FORGE's generator does for `similarity-search-api`/`ws`) is real
work with a real footgun (duplicate `ctx` parameter injection, see memory) -- deferred until this
candidate shows real MCP traffic in the 7-day window. Until then this asset can log volume
(`traffic_events`/`mcp_call_events`) but not MCP revenue.
- **No per-caller rate limiting.** Fine for a 7-day disposable measurement; add if it survives.
- **SSRF guard is IP-range based, not a full allowlist.** Rejects private/loopback/link-local/reserved
destination IPs (including cloud metadata's `169.254.169.254`) on the initial request and on every
manually-followed redirect hop (max 3). Does not defend against DNS rebinding between the check and
the actual connect (accepted risk for this candidate's scope).
## Measurement (Part 3 of the setup session)
7-day window from first real deploy. Source of truth: `traffic_events`/`revenue_events`/`mcp_call_events`
tables (`asset_name = 'url-metadata-api'`), not Cloud Run logs. Day 7: if zero real traffic (filtering
crawlers), pause/delete the Cloud Run service (`gcloud run services delete url-metadata-api --region
us-central1 --project nexus-505016`) and move to candidate #3.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues