Repository navigation
OpenAI plugin review release. Search, research, and Google scraping return real destination URLs instead of Google's relative click-tracking links; tool descriptions drop comparative wording; and the hosted endpoint serves ChatGPT/OpenAI clients a reviewed 17-tool surface on the same URL.
Changed
- Tool descriptions no longer compare options with evaluative wording (OpenAI plugin review flagged
novada_search's description as comparing/preferring; applied to every client, not only OpenAI). Removed superlatives, magnitude claims, and "cheaper/faster than X" phrasing — e.g.novada_search's "(faster, more thorough)" and "fastest/most reliable … slower fallbacks",novada_extract's "15-100x faster on static sites",novada_crawl's "novada_map — much faster",novada_research's "novada_search — faster/cheaper", the SERP scrapers' "cheaper than a raw-SERP scrape" / "raw SERP dump",novada_proxy's "strongest"/"fastest/cheapest", and "slower" in threerender/max_depthparameter descriptions. Routing guidance stays ("use novada_research for…"), and neutral facts about our own options stay ("adds rendering time").novada_scrape_perplexityno longer mentions the broken chatgpt.com scraper. Tier A tool-selection eval unchanged (60% exact / 64% acceptable, identical per-task picks). - Cross-references to sibling tools are marked up for per-surface rendering (
{{sibling:…}},_core/description_surface.ts): mentions ofnovada_scrape,novada_map, andnovada_monitorin other tools' descriptions are dropped on a surface that does not list those tools. No visible change on npm or the default hosted surface.
Added (hosted server — mcp.novada.com, not part of the npm package)
- Per-client tool surface for OpenAI (
hosted-server/vercel/api/_surface.ts). The samehttps://mcp.novada.com/mcpURL now serves OAuth clients whose registered redirect URIs are onchatgpt.comoropenai.com(exact host or subdomain, https only) a 17-tool surface: search, extract, crawl, research, the Google/Bing/DuckDuckGo/Yandex/Amazon/Walmart/Perplexity/YouTube/GitHub scrapers, proxy, ai_monitor, account, proxy_account_list. On that surface the platform scrapers are annotatedreadOnlyHint: false(each call queues an upstream scrape job), descriptions drop mentions of tools it does not list, hidden backward-compat aliases are not callable, the one-time onboarding notice (it advertises free credits) is not appended, error guidance does not point at the local npm server, andinitializereturns routinginstructions. The surface is resolved per request from the access token'sclient_id(cached per process; client records are immutable), so already-connected clients switch without reconnecting. API-key callers and every other OAuth client keep the unchanged 29-tool surface.config/surfaces.jsongains anopenaientry;scripts/check-hosted-drift.mjsgate (d) requires it to equal the code list and stay a subset ofhosted.
Changed (hosted server)
POST /registeracceptstoken_endpoint_auth_methodclient_secret_post/client_secret_basicand registers the client as public ("none"in the response, no secret issued) instead of returning 400;/tokentakesclient_idfrom an HTTP Basic header when the body omits it./mcpCORS preflight allows themcp-protocol-versionheader.- 401 responses no longer mention "$10 free credits" (they still point to https://novada.com for a key): these are pre-auth responses, so the caller's client is unknown and the wording cannot be scoped to non-OpenAI clients.
- The hosted wall-clock timeout's resume hint names the tool to re-call (the platform scraper itself), instead of always
novada_scrape.
Fixed
novada_search,novada_research, andnovada_scrape_googleno longer return Google click-tracking "goto" redirects as a relative path (e.g.link: "/goto?url=CAES..."). Reproduced live against productionscraper.novada.com(2026-11-04): a 5-resultnovada_searchcall returned 9/goto?url=occurrences, andnovada_research's extraction step — which callsnovadaExtract({url: source.url})on the raw link — failed for every such source (sources_extracted: 0 full + N snippet-only), since a relative path is not a fetchable URL. A new shared resolver (src/utils/google_redirect.ts) resolves every Google-wrapped link to its real destination. When the upstream result carries aredirect_link(https://www.google.com/url?sa=t&...&url=<token>&ved=...), its full query is replayed verbatim against Google's/urlendpoint and the destination is read from the page'svar redirectUrl/<meta refresh>— one request per result. Live-measured 2026-11-05: for fresh tokens, Google's/goto?url=<token>answered with its "verifying your request" interstitial instead of a 302 for 4/4 tokens, while the/urlreplay resolved 4/4 from both a residential IP and the production EC2 host; end-to-end, 3 spaced searches resolved 12 of 13 wrapped results (the same queries before this change: 6 of 14). The/goto?url=<token>302 remains the second path, and when it returns the interstitial the resolver follows its embedded continuation token exactly once (the token is JS-escaped asurl\x3d...in the page). A bare/url?url=<token>request (without the rest of the query) is never sent — it only ever returns a generic notice page. A result that still cannot be resolved is KEPT with its title and snippet but no link (url_unavailable: true) — never a relative path; when every result in a batch is unresolvable the response says so explicitly (url_resolution_warning). Decoding the opaque token locally (no network call) was investigated and rejected — it base64url-decodes to an encrypted protobuf blob with no recoverable URL. Wired intonovada_search/novada_research(search.ts'sparseAndResolveScraperSearchResults, the one choke point both share) and intonovada_scrape_google/novada_scrape(platform="google.com")(the genericscrape.tsengine, a separate raw-record code path). Unresolved links are counted (goto_links_unresolvedin json, an Agent Hints line in markdown/csv/html/toon). Every outbound request targets hardcodedhttps://www.google.com/{goto,url}URLs; a token is only taken from a Google redirect host, so an upstream link on any other host is never fetched. Zero network-call / zero-diff for every record whose link is already a plain absolute URL — i.e. every other platform's scrape output, and most Google results too.novada_scrape_google(andnovada_scrape(platform="google.com")) no longer leak raw base64 favicon/image data injsonandmarkdownoutput. A Google organic result'sfaviconfield (and the nestedabout_this_result.source.icon) is adata:image/png;base64,...blob, live-measured at ~19KB EACH —csv/excel/htmlalready dropped these columns viacurateTabularRecords;jsonandmarkdownrendered them raw. A new recursivestripBase64Blobs()(reusing the sameisBase64Blobdetector) replaces any base64-blob value with a short[base64 image data stripped]marker in those two formats only;csv/excel/html's existing column-drop behavior is unchanged.novada_search'stime_range/start_date/end_dateparams now actually filter Google and DuckDuckGo results. Live-verified (2026-11-04) that the previous implementation sent a literaltime_range=weekform field to the upstreamgoogle_searchscraper, which silently ignored it — atime_range="week"query returned results dated from 2025 and 2026 months out, not the last 7 days. The google.com engine now maps to Google's owntbs=qdr:{d|w|m|y}(relative) ortbs=cdr:1,cd_min:M/D/YYYY,cd_max:M/D/YYYY(custom range, whenstart_date/end_dateis given — wins overtime_range); both shapes were live-verified against known date windows. The duckduckgo.com engine now maps todf={d|w|m|y}(the catalog's own declared enum for this op). The yandex.com engine's equivalent (within, enum["0","77","1","2"]) is NOT self-evidently a day/week/month/year encoding and was left unmapped (same pre-existing, documented no-op) rather than guess at an unverified code — seereports/search-goto-links-2026-11-04.mdfor the open follow-up.