Skip to content

Releases

v0.9.46

Choose a tag to compare

@Goldentrii Goldentrii released this 06 Nov 08:42
f35b8f8

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 three render/max_depth parameter descriptions. Routing guidance stays ("use novada_research for…"), and neutral facts about our own options stay ("adds rendering time"). novada_scrape_perplexity no 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 of novada_scrape, novada_map, and novada_monitor in 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 same https://mcp.novada.com/mcp URL now serves OAuth clients whose registered redirect URIs are on chatgpt.com or openai.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 annotated readOnlyHint: 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, and initialize returns routing instructions. The surface is resolved per request from the access token's client_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.json gains an openai entry; scripts/check-hosted-drift.mjs gate (d) requires it to equal the code list and stay a subset of hosted.

Changed (hosted server)

  • POST /register accepts token_endpoint_auth_method client_secret_post / client_secret_basic and registers the client as public ("none" in the response, no secret issued) instead of returning 400; /token takes client_id from an HTTP Basic header when the body omits it.
  • /mcp CORS preflight allows the mcp-protocol-version header.
  • 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, and novada_scrape_google no longer return Google click-tracking "goto" redirects as a relative path (e.g. link: "/goto?url=CAES..."). Reproduced live against production scraper.novada.com (2026-11-04): a 5-result novada_search call returned 9 /goto?url= occurrences, and novada_research's extraction step — which calls novadaExtract({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 a redirect_link (https://www.google.com/url?sa=t&...&url=<token>&ved=...), its full query is replayed verbatim against Google's /url endpoint and the destination is read from the page's var 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 /url replay 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 as url\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 into novada_search/novada_research (search.ts's parseAndResolveScraperSearchResults, the one choke point both share) and into novada_scrape_google/novada_scrape(platform="google.com") (the generic scrape.ts engine, a separate raw-record code path). Unresolved links are counted (goto_links_unresolved in json, an Agent Hints line in markdown/csv/html/toon). Every outbound request targets hardcoded https://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 (and novada_scrape(platform="google.com")) no longer leak raw base64 favicon/image data in json and markdown output. A Google organic result's favicon field (and the nested about_this_result.source.icon) is a data:image/png;base64,... blob, live-measured at ~19KB EACH — csv/excel/html already dropped these columns via curateTabularRecords; json and markdown rendered them raw. A new recursive stripBase64Blobs() (reusing the same isBase64Blob detector) 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's time_range/start_date/end_date params now actually filter Google and DuckDuckGo results. Live-verified (2026-11-04) that the previous implementation sent a literal time_range=week form field to the upstream google_search scraper, which silently ignored it — a time_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 own tbs=qdr:{d|w|m|y} (relative) or tbs=cdr:1,cd_min:M/D/YYYY,cd_max:M/D/YYYY (custom range, when start_date/end_date is given — wins over time_range); both shapes were live-verified against known date windows. The duckduckgo.com engine now maps to df={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 — see reports/search-goto-links-2026-11-04.md for the open follow-up.

v0.9.45

Choose a tag to compare

@Goldentrii Goldentrii released this 01 Nov 12:04
22fe63f

Credential-safety and pricing-truth release. Proxy dial credentials are masked everywhere by default and can be revealed only on the local server with an explicit user confirmation; the hosted endpoint drops the obsolete monthly free-call cap and serves the read-only static-IP functions.

Added

  • novada_static_ip_mgmt gains a reveal_credentials action, local MCP server only (owner decision 2026-11-01, option A; final scope per a same-day follow-up decision below): static-ISP dial credentials stay masked everywhere by default, but a user who explicitly asks (e.g. to configure their own static IPs in AdsPower or another proxy-aware tool) can get the REAL account/password pair through this read-only action — gated behind the same two-step approval_token flow as open/renew (evaluateApprovalGate, ../utils/approval.ts), or a single MCP-elicitation-confirmed call on clients that support it (see the Security entry below). Filters are identical to list (status/region/key_word/page/limit/is_auto_renew); limit is additionally capped at 50 (the response says so when it clamps a higher requested value). The preview call (no approval_token) shows masked ip/port/region/expire_time rows only — no credentials — plus a count and a warning that the real credentials may be retained in the AI client's own chat history. The execute call (with the matching approval_token) returns {ip, port, region, expire_time, username, password} per row plus two ready-to-paste formats, dial_string (ip:port:username:password) and proxy_url (http://username:password@ip:port), built only from fields the API actually returned. Not available on the hosted endpoint — see below.

Changed

  • novada_static_ip_mgmt's plain read-only actions are now served on the hosted endpoint (mcp.novada.com): list/export are dispatchable there like any other tool (hosted tool count 28 → 29). Its two billable WRITE actions, open and renew, are still refused on hosted — and so, per a 2026-11-01 FOLLOW-UP, FINAL owner decision, is reveal_credentials: real dial credentials need a human-in-the-loop confirmation (MCP elicitation) that the stateless hosted endpoint cannot perform (see the Security entry below), and falling back to an agent-carried approval_token alone there would be the exact hole elicitation exists to close locally, so it is refused outright rather than served with a weaker guarantee. All three actions are refused at the ACTION level, inside the tool itself (assertActionAvailableOnRuntime(), thrown as a NovadaError before either the approval gate, elicitation, or any upstream call runs), not via whole-tool hiding. novada_static_ip_mgmt was removed from HOSTED_HIDDEN (hosted-server/vercel/api/mcp.ts) and its mirror (novada_discover's HOSTED_HIDDEN_TOOLS, npm-package/src/tools/discover.ts).

Changed (hosted server — mcp.novada.com, not part of the npm package)

  • The monthly free-call gateway cap is removed entirely (owner decision, 2026-10-29). mcp.novada.com's "1,000 free calls per token per month" limit was a one-time new-user promotion that has ended, not a recurring allowance — every call is now billed to the caller's own Novada account, same as calling the REST API directly; new accounts still get $10 free credits. Removed: the per-token monthly KV quota counter (decrementQuota/refundQuota/monthKey), the paid-tier plan resolution and over-cap exemption (enforceGatewayCap, resolvePlan, shouldAllowOverCap, classifyPlanFromUsageRecord, fetchAggregateBalance/parseBalanceValue, the approval-gate preview-charge exemption), and the FREE_PLAN_MONTHLY_QUOTA env var (no longer read). CAP_EXEMPT_TOOLS/ACCOUNT_TOOL_NAMES survive unchanged and now drive only the gateway status footer. Auth, rate limiting (60 req/min/IP), OAuth, and token caching are unchanged.
  • Gateway status footer collapses from three variants to two. The old "⚠ gateway: N/M free calls remaining this month · cost: unknown…" and "gateway: uncapped (paid account) · cost: unknown…" lines are gone. Billable tools now carry "gateway: usage is billed to your Novada account — balance and charges at dashboard.novada.com"; CAP_EXEMPT_TOOLS (novada_setup, novada_discover, novada_account + aliases) carry "gateway: no charge for this call". The over-cap rejection message ("## Free Gateway Cap Reached") and its _meta.quota_remaining success-path field are both gone.
  • Telemetry rows keep their schema (no DB migration) — charged/over_cap_allowed/quota_remaining/plan are now always null ("no cap applies") instead of a free/pro classification; validateToken no longer returns a meaningless quota_remaining. The HQ push payload shape is unchanged (buildHqPayload already defaulted these to 0/"" for a null row).
  • Updated the gateway's nightly contract canary (contract-test.py): COST_VISIBILITY now checks the two-variant footer contract; BILLING_TRUTH (which guarded a funded key against the now-removed cap) is deleted.
  • Docs/landing pages that claimed a free monthly call count (hosted-server/docs/*, hosted-server/landing/*, hosted-server/vercel/README.md, hosted-server/smithery.yaml) now describe usage-based billing with a $10 new-account credit instead.

Security

  • Static ISP IP dial credentials (account/password) are now masked in novada_static_ip_mgmt (list/export/open/renew) and in novada_plan_balance_all/novada_account(section="plans"/"summary")'s per-IP raw field — both hit the same /v1/static_house/list endpoint, which returns a per-IP dial username/password pair in cleartext. A new shared helper, maskCredentials() (_core/developer_api.ts), recursively masks a table-driven set of credential-bearing field names (password/passwd/pwd/pass/secret/token/api_key/apikey → ***; account/username/user_name → <PROXY_USER>, matching novada_proxy's existing convention) without touching IPs, ports, regions, statuses, or expiry dates. sanitizeServerMsg (_core/errors.ts) gained a matching rule so a credential echoed back in an UPSTREAM ERROR message is masked too, not just the success path. novada_capture_apikey's own existing ****<last4> masking is unchanged (out of scope — already correct for both its get and reset actions).
  • access_token added to the masked fields, plus a drift guard: a test reads the local developer-API/scraper-API reference mirror and fails if any credential-looking field name there is neither masked nor explicitly listed as safe (with a reason), so a future field such as dial_password cannot slip past the exact-match table unnoticed.
  • evaluateApprovalGate()'s signed payload now binds the caller's own tool name (../utils/approval.ts, 2026-11-01 follow-up to an independent security review of reveal_credentials): a new required toolName argument (e.g. "novada_static_ip_mgmt") is folded into the HMAC-signed canonical payload as { ...params, __tool: toolName } for all four gated tools (proxy_account_create, ip_whitelist add/del/remark, capture_apikey reset, static_ip_mgmt open/renew/reveal_credentials). Previously cross-tool replay was prevented only because each tool's gated params happened to differ in shape — a token minted for one tool could, in principle, verify unmodified against a different tool whose params object reduced to the same bytes (most exposed: proxy_account_create, which has no action discriminator field at all). A table-driven test now cross-checks every real gated call site pairwise.
  • novada_static_ip_mgmt's reveal_credentials action now uses real MCP elicitation as the human confirmation on the local stdio server, when the connected MCP client advertises elicitation support (clientCapabilities.elicitation.form, negotiated at initialize). Previously the only confirmation mechanism was a two-step approval_token round-trip that passes THROUGH the agent — a prompt-injected or careless agent could simply make both calls itself with no human ever seeing the request. With elicitation: a single tool call fetches the filtered IP count, sends the client a human-facing confirmation (count + a warning that credentials will appear in the conversation and may persist in the client's own chat history) via server.elicitInput, and only an explicit accept reveals the real username/password; decline/cancel/an elicitation-transport failure all return a non-throwing status:"declined" response with no credentials anywhere in it. approval_token is not read at all on this path — a pre-minted or replayed token has zero effect and cannot bypass the human-in-the-loop step. Local clients without elicitation support keep the unchanged approval_token fallback. The hosted endpoint, always — mcp.novada.com builds a fresh Server+StreamableHTTPServerTransport per stateless HTTP request and closes it again before any client reply could arrive, so a server-initiated request needing a later reply cannot complete there — cannot perform elicitation at all; per a same-day FOLLOW-UP, FINAL owner decision, falling back to the agent-carried approval_token alone on hosted was judged NOT an acceptable substitute (it would reopen the exact hole elicitation exists to close), so reveal_credentials is refused outright on hosted instead (see the Changed entry above) rather than left on that fallback. The capability check and elicitInput call live only in the stdio entry point (index.ts) and are injected into the tool as a plain callback (ElicitConfirmFn) — static_ip_mgmt.ts has no MCP-SDK/transport dependency and no forked logic between stdio and hosted.

Fixed

  • novada_static_ip_mgmt's list/export default to status="1" (in-use) when the caller omits status, instead of returning every IP the account has ever owned (including long-expired/released ones). A new summary field re...
Read more

v0.9.44

Choose a tag to compare

@Goldentrii Goldentrii released this 26 Oct 16:34
86c86f7

Error-honesty and listing release. Scraper HTTP failures now come back as structured errors on both the npm package and mcp.novada.com, and the package/registry listing carries the new one-line description.

Changed (npm package)

  • New package description and MCP Registry listing. package.json and server.json now read "One MCP server for all web data. Scrape, search, crawl, and AI research in one install."; server.json adds title ("Novada MCP") and websiteUrl. The registry description is generated by scripts/gen-server-json.mjs, so the change lives there.

Fixed

  • novada_scrape HTTP errors (429/5xx) from the submit and result-retrieval steps are now structured, honest errors instead of a plain, unclassified string. scrape.ts's submit/poll catch blocks now classify an AxiosError with a real HTTP response through the shared classifyError()/classifyScraperHttpError() taxonomy (429 → RATE_LIMITED with retry guidance; 402/11006 → PRODUCT_UNAVAILABLE; 5xx → API_DOWN; 401 → INVALID_API_KEY; 403 → transient API_DOWN at both steps — previously only at submit, so a result-step 403 was reported as a permanent unknown error) at the throw site, with resume-by-task_id guidance when a task already exists. Root cause: the hosted gateway (hosted-server/vercel/api/mcp.ts) never called classifyError() on a thrown plain Error — only the npm-package's local stdio dispatch did — so a 429 from the result-retrieval step reached hosted callers as a bare "Tool error: Failed to retrieve scraper results: Request failed with status code 429" with no code, failure class, or agent_instruction. mcp.ts's generic error fallback now routes through the SAME classifyError() the stdio dispatch already uses, fixing this class of gap for every tool, not just novada_scrape.
  • Nightly canary's NO_LYING_ZERO (invariant 3) upstream-signal recognizer now includes 429/rate-limit (hosted-server/scripts/contract/contract-test.py) — it already promised "Upstream flakiness (timeout, backend error, rate-limit) → SKIP" in its own docstring but never implemented the rate-limit case. Covered by two new --self-test scenarios ((h)/(i), a mirror pair).

Fixed (hosted deploy tooling — not part of the npm package)

  • deploy-hosted.sh records the real rollback target. It used to print the deployment it had just shipped as the "rollback target"; it now reads the deployment serving mcp.novada.com before deploying. Its live-version probe now sends the Accept header Streamable HTTP requires (it previously always printed UNKNOWN).

v0.9.43

Choose a tag to compare

@Goldentrii Goldentrii released this 26 Oct 12:50
deb9012

Documentation-truth and hygiene release after an end-to-end product evaluation. Tool behaviour is unchanged except where a tool's own text or output framing was wrong.

Changed (npm package)

  • novada_proxy says what it returns. Its description, the novada_discover catalog line, the CLI help and the novada-agent skill now state that the tool returns a proxy config template — username shown as <PROXY_USER>, password as *** — and never prints credentials. (The output was already masked; the text claimed otherwise.)
  • novada_setup never names a tool the session cannot call. On the hosted surface it no longer lists novada_browser or the Browser-WS add-on. novadaSetup() takes an optional visible-tool set as a third argument, the same mechanism novada_discover already used.
  • novada_discover reports 9 hosted-hidden tools (including novada_browser), derived from one exported list (HOSTED_HIDDEN_TOOLS) that a test keeps equal to the hosted server's HOSTED_HIDDEN set.
  • Developer-API 401 errors point at https://dashboard.novada.com/api-key/ instead of an internal host that the redactor turned into [novada-internal-host].
  • The shipped novada-proxy skill refers to novada_proxy with type=… instead of six tool names that no longer exist.
  • Smaller tarball: sourcemaps are no longer published (!build/**/*.map), 439 → 225 files. The dead benchmark npm script (no directory, tsx never a dependency) is removed.
  • Removed scripts/verify-c1-live.mjs (one-off probe with a hard-coded macOS path; tracked despite the repo's own .gitignore rule, unreferenced).

Added

  • Official MCP Registry publishing via GitHub Actions OIDC. A manually triggered, secretless workflow validates and publishes server.json under the repository-owned io.github.NovadaLabs/* namespace with only contents: read and id-token: write permissions.
  • Root LICENSE (MIT) so GitHub detects the license.
  • Tests: hosted-surface novada_setup visibility, hosted-hidden list parity, 401 message wording, novada_extract format=json output parses as JSON.

Fixed (hosted server — mcp.novada.com, not part of the npm package)

  • JSON results are no longer corrupted by the gateway: footer. When a tool result is JSON (format=json), the footer is returned as a second content block instead of being appended to the JSON text (api/_footer.ts, 7 tests).
  • Nightly canary no longer reports the canary account's own expired plan as a production failure. A truthful PRODUCT_UNAVAILABLE refusal with ledger evidence is classified XFAIL: CANARY_ACCOUNT_PLAN (issue #18) in scripts/contract/contract-test.py; every other error still fails.
  • vendor/package.json version drift. deploy-hosted.sh and sync-to-hosted.mjs now keep the vendor shim's version equal to the npm package (it had reported 0.9.3 since June; the vendored config.js reads it at runtime). Vendor re-synced in this release.

Docs

  • README rewritten (164 → 81 lines): outcome line, what the server is good at, example prompts, hosted quickstart, an honest comparison with Firecrawl, Tavily and Bright Data. Removed claims that did not match the code: the confirm:true write-gate (writes use a two-step approval token), "6 categories", "4.9× more compact", and browser automation on the hosted surface. npm-package/README.md links are absolute so they work on npmjs.com.
  • docs/TOOLS.md and ARCHITECTURE.md corrected (14 working platforms / ~76 operations, hosted browser exclusion, approval-token wording); CLAUDE.md lists the real top-level directories; HANDOFF.md / PROJECT_STATE.md marked historical; CONTRIBUTING.md carries the release checklist (the pre-monorepo promote-to-public.sh is no longer referenced).

Full evaluation that drove this release: see PR #27.