MCP
ScriptHaul has two deliberately different Streamable HTTP MCP endpoints.
| Endpoint | Authentication | Intended use |
|---|---|---|
https://scripthaul.com/mcp | None | A small keyless taste: 20 cold fetches per address per UTC day; bulk links back to the free site. |
https://api.scripthaul.com/mcp | OAuth or API key in Authorization: Bearer | The account's REST limits, balance, metering, and keyed bulk jobs. |
Set up your assistant
- Claude on the web and Desktop: remote custom connector with OAuth.
- Claude Code: one command, then browser sign-in.
- Cursor: one-click install and configuration JSON.
- VS Code: one-click install and configuration JSON.
- ChatGPT: developer-mode connection with OAuth.
- Windsurf: OAuth or an environment-supplied API key.
- OpenAI Agents SDK: a server-side agent with hosted MCP tools.
- n8n: the Streamable HTTP MCP Client Tool.
OAuth opens ScriptHaul's email-code sign-in and a consent screen naming the client, the redirect destination host, and the scope mcp. Review that destination before approving. A grant can use account tools, create jobs, and spend credits; it cannot see or create API keys. Revoke access in Dashboard → Connected apps. Clients discover the authorization server through the unauthenticated MCP challenge and protected-resource metadata. WWW-Authenticate exposes that resource-specific URL to browser clients through CORS. API keys continue to work as Bearer credentials.
All OAuth grants for one account share one tier allowance pool for request rate, cold-fetch and search concurrency, and daily limits. Usage from revoked grants still counts; ordinary API keys retain their independent per-key limits. Search's 60-second free replay stays specific to the same grant. There are at most ten connected grants; completing another connection revokes the oldest by creation date. Reconnect when prompted, no later than 30 days: refresh does not extend the grant's original expiry, and client registration can expire sooner. Revoking a grant blocks refresh immediately, while an already memoized access token elsewhere may work for up to 30 seconds. See authentication for the connection lifecycle and separate token/registration limits.
Tools and costs
The keyless server never accepts an API key. The keyed server is registered as com.scripthaul/api and exposes get_transcript, list_videos, get_bulk_download_link, create_bulk_job, get_job, search_youtube, resolve_channel, get_latest_videos, get_library, search_transcripts, watch_channel, and list_monitors. The download-link tool only returns a prefilled link to the free bulk site; it does not create a keyed job. Transcript and listing behavior matches REST: cache hits, metadata, and enumeration cost zero; one successfully delivered cold transcript costs one credit, and with translate: true a transcript delivered in a language YouTube declined to translate costs 300 credits per started 10,000 characters of transcript (at most 1,500), on top of the delivery credit (details). create_bulk_job takes the same translate: true for every video of the job (details). Ask the user before setting translate: true on either tool. A keyed bulk call creates the same durable job as POST /v1/jobs, including reservations and account-scoped idempotency. create_bulk_job and get_job return the job envelope without per-video rows; page GET /v1/jobs/{id}/videos (with status=error for failures) when an agent needs them.
Use search_youtube to find videos by topic or search inside a channel: one credit per delivered page, with an identical page repeated using the same key within 60 seconds free. Per key and UTC day, search allows 50 delivered pages and 150 admitted attempts for free keys, or 2,000 pages and 6,000 attempts for paid keys. Admitted failed attempts count but cost zero credits; denied admission, in-progress duplicates, and free replays do not consume another attempt. search_in_progress is retryable after one second; search_attempt_cap requires waiting until UTC midnight.
resolve_channel and get_latest_videos cost zero credits. On both servers, get_latest_videos accepts exactly one channel or playlist string (1–300 characters), with no additional fields. Playlist IDs and equivalent YouTube playlist URLs share the RSS cache; channel and playlist calls share the latest-upload allowance. Resolution allows 100 calls per free key per UTC day or 1,000 per paid key; latest uploads allows 500 free or 5,000 paid calls/day. Valid admitted calls count even on warm results. The keyless server also has get_latest_videos, which reads the newest 15 RSS entries with a separate allowance of 30 calls per IP address/day, including feed-cache hits, without consuming its cold-fetch allowance. See search and channels for inputs, pagination, and examples.
For protocol version 2025-03-26, a keyed JSON-RPC batch may contain at most two tools/call messages. Each uses its own request-rate token; both tokens must be admitted before either tool runs. Three or more tool calls are refused. Billing and replay apply to each search page independently, even inside one batch. Each tool has its own execution ID while the HTTP response keeps one request ID for tracing. This batch allowance does not add batching to protocol versions that do not support it.
For library questions, call get_library(channel) first. Coverage costs zero credits and allows 200 calls per free key per UTC day or 2,000 per paid key, including warm snapshots. Explain counts.cold, fetch the missing selection through create_bulk_job when authorized, and recheck after indexing. search_transcripts(query, channel?, video_id?, limit?) returns passages from exactly one channel or video at zero credits, with 500 searches per free key per UTC day or 20,000 per paid key. These daily limits are checked before upstream work; on QuotaExceeded, follow the retry timing until UTC midnight. The ordinary key request rate also applies. Qualify the returned coverage and cite each passage's timestamp url; never paste whole transcripts unless asked. The library is keyed and has no public or cross-channel search.
Monitor tools are watch_channel(channel, auto_fetch?, language?) and list_monitors(). Watching costs zero credits and baselines the current RSS feed. auto_fetch defaults to false; enabling it authorizes future ordinary single-video jobs and their cold-delivery charges. Free accounts have one monitor and paid accounts have 25. Use the dashboard or REST to cancel future checks.
Never put a key in the MCP URL or client arguments that may be logged. Use an HTTP header supplied from the client's secret or environment configuration. The server rejects query-string credentials.
{
"mcpServers": {
"scripthaul": {
"type": "http",
"url": "https://api.scripthaul.com/mcp",
"headers": { "Authorization": "Bearer ${SCRIPTHAUL_API_KEY}" }
}
}
}
Machine discovery
Agents can discover the ScriptHaul transcript skill at /.well-known/agent-skills/index.json, or install it with one command. Its SHA-256 digest pins the exact SKILL.md bytes. The skill says when to choose keyless MCP, keyed MCP, or REST and explicitly excludes video and audio download.
The keyed remote publishes an experimental MCP server card at /mcp/server-card with OAuth discovery metadata, current tool names, and an optional API-key header template. OAuth clients leave that template empty and use the discovered authorization flow. The card format is an experimental MCP extension and may change.
List keyed tools in four languages
Maintained runnable versions are in the examples directory.
curl https://api.scripthaul.com/mcp -X POST -H "Authorization: Bearer $SCRIPTHAUL_API_KEY" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
import json, os, urllib.request
body = json.dumps({"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}).encode()
request = urllib.request.Request("https://api.scripthaul.com/mcp", data=body, method="POST")
request.add_header("Authorization", f"Bearer {os.environ['SCRIPTHAUL_API_KEY']}")
request.add_header("Content-Type", "application/json")
print(urllib.request.urlopen(request).read().decode())
const response = await fetch("https://api.scripthaul.com/mcp", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.SCRIPTHAUL_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} }),
});
console.log(await response.json());
body := strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}`)
req, _ := http.NewRequest("POST", "https://api.scripthaul.com/mcp", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("SCRIPTHAUL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
response, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer response.Body.Close()
io.Copy(os.Stdout, response.Body)