NessGate
The open, neutral compatibility resolver for the agentic web. Give NessGate a domain and itreads whatever that domain already publishes — across ARD (all three surfaces), A2A, llms.txt,RFC 9727 api-catalog, Open Resource Discovery, RFC 6415 host-meta, OpenAPI, Agent Network Protocol(ANP), Universal Commerce Protocol (UCP), DNS-AID and more — and returns one normalized answer,with a link back to each source so an agent can always verify against the domain itself.
company.com → { resources: [
{ source: "ard-catalog", type: "application/json",
url: "https://company.com/ai-info.json",
sourceUrl: "https://company.com/.well-known/ard.json" },
... ] }
One call instead of ten. NessGate reads these standards; it does not define or replacethem — a new standard is just a new adapter, never a competitor. It reuses each source's owntype labels and invents no taxonomy of its own. The domain is always the authority;NessGate only normalizes what the domain already publishes, reads it on demand, and stores nothing.
Live at https://nessgate.com · Specification ·Charter · API
Use it
Embeddable library — dependency-free, fetches the target domain directly (no runtimedependency on nessgate.com), runs anywhere with fetch — Node, Deno, Workers, and agent runtimes.(It runs in a browser too, but a browser can only read other domains that send CORS headers, andmost .well-known files don't — so from a browser, resolve arbitrary domains via the hostedendpoint below, which sends open CORS.) Published as@nessgate/resolver:
import { resolve } from "@nessgate/resolver"; // or "https://nessgate.com/resolver.mjs"
const { resources } = await resolve("example.com");
Hosted endpoint — open CORS, no auth:
curl https://nessgate.com/discover/example.com
MCP — the same lookup as a tool (discover_domain) at https://nessgate.com/mcp. Listed in theofficial MCP Registryas com.nessgate/nessgate (domain-verified remote server), so MCP-aware clients can install it directly.
Integrate it (≈5 lines)
Give an agent a domain, get back what to use — no per-standard code. Drop this into a tool,a retrieval step, or an onboarding flow:
import { resolve } from "@nessgate/resolver"; // dependency-free, no key, no account
const { resources } = await resolve(domain); // reads the domain directly
for (const r of resources)
console.log(r.type, r.url, "←", r.sourceUrl); // normalized record + where it came from
// each r: { source, type, url, sourceUrl } — pick the one your agent needs (OpenAPI, A2A, MCP, …)
No SDK? The hosted endpoint is one HTTP GET (GET https://nessgate.com/discover/{domain}, openCORS, no auth), and the MCP tool discover_domain returns the same shape. Adding a new standardis a new adapter upstream — integrations don't change.
Full integration guide — library, HTTP, and MCP client config (including the mcp-remote bridgefor stdio-only clients): docs/integrations.md.
Principles
NessGate is free, neutral infrastructure — see the Charter. Itnever charges to use or to be read, never sells ranking or placement (there is none), keeps noaccounts, and stores no domain data. It reads a domain on demand (answers are cached at the edgefor up to 10 minutes), never crawls or indexes, and makes no ownership or safety claim — it reportswhat a domain serves and links back to each source. The specification is open and the referenceimplementation is Apache-2.0 licensed:anyone may run their own resolver, and if nessgate.com disappeared, every domain's files wouldstill stand on the domain itself.
Architecture
One stateless Cloudflare Worker (
src/worker.js) serves the static site(public/, via the assets binding withrun_worker_first), the resolver API, the MCPserver, the per-domain pages, and the sitemap. There is no database.The resolver (
GET /discover/{domain}, and the embeddablepublic/resolver.mjs) readswhat a domain publishes, normalizes it into one answer, fetches the domain directly, and storesnothing. Answers are computed fresh and cached at the edge for 10 minutes. A parity test keepsthe worker's and the library's normalization byte-identical, and keepspackages/resolver/index.mjs(the npm package) byte-identical topublic/resolver.mjs.Adapter architecture — four discovery channels. Each supported standard is a small,independent adapter, and every adapter uses one of four channels to locate its document:
- well-known — GET a fixed path (or paths) on the domain:
llms.txt,ard-catalog(ARD /ai-catalog),a2a-agent-card(A2A),api-catalog(RFC 9727),ai-info.json,openapi,ord(Open Resource Discovery),awp(draft),host-meta(RFC 6415),anp(Agent Network Protocol/.well-known/agent-descriptions), anducp(UniversalCommerce Protocol/.well-known/ucp). - link-rel — parse
<link rel="ard">in the homepage, then GET the target (ard-link). - robots — parse an
Agentmap:directive in/robots.txt, then GET the target(ard-agentmap). - dns — a DoH TXT lookup at
_agent.<domain>(dns-aid:v=aid1;u=<uri>;p=<proto>;a=<auth>).
Complete ARD support means all three ARD surfaces: the well-known paths, the
<link rel="ard">tag, and the robots.txtAgentmap:directive. ANP and UCP are emerging;DNS-AID/AID and AWP are drafts, described as such and read as-is with no adoption claim.- well-known — GET a fixed path (or paths) on the domain:
GB/Z 185.4 / 185.5 is not implemented. China's 智能体互联 agent description/discovery standardsare not supported: the discovery mechanism is defined only in the paywalled Chinese nationalstandard and appears to be a federated discovery service rather than a domain-native path, sothere is no concrete surface to probe. The adapter architecture is ready to host it once theendpoint is verified; NessGate makes no GB/Z claim in the meantime.
Cloudflare KV (
NESSGATE_KV) holds only approximate, IP-keyed hourly rate-limit countersthat expire within the hour. Nothing else is stored.Rate limiting: a Cloudflare-native edge limiter (burst, per-colo and eventuallyconsistent — approximate by design) in front of an approximate KV hourly cap. Abuseprotection, not exact global accounting.
SSRF protections: DoH pre-check against private/reserved IPs, on-domain redirects only(≤ 3), 1 MB caps, 8 s timeouts, HTTPS-only. Probes are read-only GETs of public well-knownpaths; the DNS-rebinding TOCTOU window is documented in
src/worker.jsand is immaterialhere (Worker egress has no private network behind it, and probes assert nothing).No accounts, no emails, no stored domain data.
Endpoints
| Endpoint | Purpose |
|---|---|
GET /discover/{domain} |
The resolver. Reads what the domain publishes across the supported adapters (llms.txt, ARD/ai-catalog via well-known paths, rel="ard" link, and robots Agentmap:; A2A agent card, RFC 9727 api-catalog, ai-info.json, OpenAPI, ORD, AWP, host-meta, ANP, UCP, DNS-AID) and returns one normalized answer — {domain, provenance, note, discovered[], resources[], checked[]}, each resource carrying its source and native sourceUrl. CORS open, no auth; nothing stored or crawled; 10-min cache, 120/hr/IP. |
POST /mcp |
Model Context Protocol server (Streamable HTTP, stateless, no auth) exposing one tool, discover_domain, that returns the same answer as /discover. |
GET /{domain} |
Human-readable domain page — the resolver rendered for humans (live discovery). |
GET /resolver.mjs |
The embeddable resolver library (also on npm as @nessgate/resolver). |
GET /openapi.json, /llms.txt, /spec, /sitemap.xml, /robots.txt, /.well-known/security.txt, /.well-known/api-catalog |
Machine discovery & docs |
Operations runbook
- Deploy flow: commit →
npm run deploy(runs the full regression suite as a hardpre-deploy gate, then stamps the build with the git SHA viaBUILD_ID) →npm run check(regression tests + live smoke checks, including proof that/versionon production equalslocal HEAD) → push. CI (GitHub Actions) runs the regression suite on every push. Thedeploy-script gate is the effective production gate, since deploys run from the workstation. - Build verification:
GET /versionand theX-NessGate-Buildheader on every responseidentify the exact deployed commit. - Tests:
npm test— no-network regression suite for the security-critical logic(normalization, SSRF/private-IP detection, probe-content validation, thin normalization, andworker↔library↔npm parity). - Rollback:
npm run rollback(ornpx wrangler rollback [version-id]; versions listed bynpx wrangler deployments list). - Logs:
npx wrangler tail nessgate. - CSS changes: bump the
?v=Non the stylesheet link in all pages (assets are cached;unversioned CSS changes will not reach browsers).
Configuration
wrangler.toml binds: assets (run_worker_first), KV, and a [[ratelimits]] binding. Thereare no D1 databases, no cron triggers, and no secrets — the resolver is stateless.
npm package
packages/resolver/ is published as @nessgate/resolvervia GitHub Actions Trusted Publishing (OIDC, tokenless, with provenance) — see.github/workflows/publish-resolver.yml. index.mjs is kept byte-identical topublic/resolver.mjs by the parity test.
License & contributing
The reference implementation is licensed under Apache-2.0 (LICENSE); the WordPressplugin is GPL, as required by WordPress.org. The protocol is open and independentlyimplementable — see CONTRIBUTING.md, the naming/trademark policy in TRADEMARK.md, and theCharter. Anyone may build a compatible resolver without askingpermission; independent implementations are a goal, not a threat. The canonicalresolver/normalization logic lives in public/resolver.mjs and src/worker.js (keptbyte-identical by a parity test).
Security contacts
[email protected] (see /.well-known/security.txt),[email protected], [email protected], [email protected].