@nessgate/resolver
v1.22.2
Published
Open, neutral universal resolver for the agentic web: give it a domain and get one normalized answer with the machine-readable resources it publishes across ARD (well-known, rel=ard, robots Agentmap), A2A, llms.txt, API catalogs, OpenAPI, ORD, host-meta,
Maintainers
Readme
@nessgate/resolver
Give it a domain and your client's capabilities, and it tells you how that client can connect —
and what's still missing. Dependency-free, decentralized (fetches the target domain directly, no
runtime dependency on nessgate.com), and neutral (reads the standards a domain already publishes;
invents no taxonomy). Runs anywhere with fetch: Node ≥18, Deno, Bun, Workers, agent runtimes.
Install
npm install @nessgate/resolverConnect
import { plan } from "@nessgate/resolver";
const p = await plan("supabase.com", { supports: [{ protocol: "mcp", auth: ["oauth2", "none"] }] });
p.outcome; // "ready" | "credentials-required" | "incomplete" | "broken" | "no-compatible-method"
p.connection; // { protocol, endpoint, transport, version, auth:{ type, tokenEndpoint, scopes, … } }
// → everything needed to connect; you supply your own credentials (never sent to anyone).
p.unmatched; // what the service offered that your client can't use, and vice-versaFive honest outcomes, never a guess. plan() discovers the domain, matches it against your client's
declared capabilities (deterministic intersection — no scores), and resolves each protocol's real
connection details from the service's own published metadata (OpenAPI servers+securitySchemes,
A2A cards, the MCP OAuth chain RFC 9728 → RFC 8414). Read-only; credentials stay with you.
Want just one resource's readiness? assessReadiness(resource, { fetch }). Want the raw list of
everything a domain publishes? resolve(domain) — the original resolver, below.
Self-test your own domain (CI)
npx @nessgate/resolver yourdomain.com # bin: nessgate-ready — exit 0 = connectableRuns the open readiness predicate client-side (nothing is sent to nessgate.com or anywhere else):
discovers what your domain publishes, assesses every connectable endpoint, repeats the observation
3× (any disagreement = unstable = fail — a flaky endpoint must not pass CI), and exits non-zero
with the exact missing field when your publication is incomplete or broken. Drop it in CI:
- run: npx @nessgate/resolver yourdomain.com # fails the build until agents can connect to youOptions: --observations=N · --spacing=SECONDS (spaced observation runs) · --json.
The predicate and its observation semantics are published in the NessGate repo
(docs/readiness-observation-semantics.md) — anyone can re-run it and get the same verdict.
Discover (the raw list)
import { resolve } from "@nessgate/resolver";
const { resources } = await resolve("example.com");
for (const r of resources) {
console.log(r.source, r.type, r.url); // e.g. "ard-catalog" "application/json" "https://example.com/ai-info.json"
}It reads ARD / ai-catalog.json (well-known path, rel="ard" link, robots Agentmap:), A2A agent
card, llms.txt, RFC 9727 api-catalog, ai-info.json, OpenAPI, ORD, AWP, RFC 6415 host-meta, ANP,
UCP, the AID TXT record, and GB/Z 185.4 — one normalized list, each record carrying its source and
native sourceUrl so you can verify against the domain directly.
Optional discovery extensions
const r = await resolve("example.com", { registry: true, delegate: true });registry: true— one bounded request to the official MCP Registry for the domain's exact reverse-DNS namespace. Matching servers are added as records with evidencenamespace-verifiedand an attribution naming the registry as the verifying party. If the registry cannot be checked, the response carriesfederatedUnavailable: ["mcp-registry"]instead of a silently empty result.delegate: true— follows the machine-readable pointers the domain's own documents name (an llms.txt index's links; catalog entries pointing at other locations), within fixed depth/request/host/byte limits. Followed records carry evidencepublisher-declared, the pointer chain inprovenance, anddepth; walk accounting (and any truncation) is reported indelegation.
Both options default off; output without them is unchanged. plan() accepts the
same options and passes them through.
resolve(domain, opts) returns
{ domain, provenance, discovered, resources, checked, outcome, blockedProbes? }.
outcome— the single honest label:"found"(validated resources exist),"none-found"(the checks completed — clean 404s and nonexistent hosts count as answers — and no supported declaration exists at the locations checked), or"blocked"(refusals — auth walls, rate limits, timeouts, network failures — were at least as common as clean answers: the checks were prevented, so absence is unknown).blockedProbesdiscloses the refusal count whenever any probe was refused, even undernone-found.resources— the normalized union of what the domain publishes. Each record hassource,sourceUrl,type(the source's own label),url, aclass(how far NessGate verified it), and, where useful,name/rel/id/raw.discovered— the routing map: which standards were found, and where.checked— the standards probed.
Declared capabilities (verbatim, never inferred)
Where a source document itself declares operations or skills, the record carries a
capabilities array copied 1:1 from the publisher's own words:
- OpenAPI — one entry per declared operation (
method,path,operationId,summary), plus asecurityarray from the spec's owncomponents.securitySchemes/ Swagger-2securityDefinitions. Operations are enumerated only from a complete document (one extra bounded read up tomaxByteswhen the spec exceeds the 64 KB detection prefix); a spec larger than the cap stays detected but not enumerated — never partially extracted. - A2A / GB-Z agent cards — the card's own
skills(id,name,description,tags); the card-level capabilities object rides along inraw. - ARD entries already carry their declared media types and metadata.
- MCP (opt-in) — pass
{ mcp: true }to introspect declared MCP endpoints with the protocol's own read-only handshake:initialize+tools/listonly — nevertools/call, no credentials (an auth wall is reported asintrospection.status: "auth-required"), POSTs never follow redirects, HTTPS endpoints on the queried registrable domain only, max 3 per resolution. The server's declared tools (name,description,inputSchema) land verbatim incapabilities;introspectioncarries the negotiated protocol version andserverInfo; the result is labeled top-levelintrospected: ["mcp"].
NessGate never infers, renames, or classifies a capability. capabilities is absent
when the source declares none; lists are capped at 40 and flagged
capabilitiesTruncated when the publisher declares more.
Redirects and the class label
The library follows redirects and records the final URL as sourceUrl. A fetched
document whose final URL crossed to a different registrable domain (e.g. a wholesale
rebrand like neon.tech → neon.com) is labeled class: "verified-external-location" —
never verified-publisher-location. The other classes are
publisher-declared (declared in a fetched catalog, same registrable domain, target not
fetched), declared-external-pointer (declared, different registrable domain,
unverified), and unsupported (no usable URL). The hosted resolver at nessgate.com is
domain-locked and never follows cross-domain redirects, so verified-external-location
appears only in library results.
Options: { fetch, timeoutMs = 8000, maxBytes = 1_000_000, deadlineMs = 20000, org, fast, mcp, verify }.
Bring your own fetch if the runtime has none.
verify: true— a labeled reachability pass: surfaces this resolution already fetched arereachability: "ok"with no extra request; MCP records reuse introspection results; declared pointers get one safe probe each (HEAD, then a bounded GET when HEAD is unsupported or a denial is unexplained — never a POST, never an execution), capped at 8. States:ok | auth-required | rate-limited | blocked | not-found | unreachable | unknown, pluscheckedAtandevidence({status, signal name, retryAfterSeconds} — response content is never stored).auth-requiredneeds protocol evidence (401/407 orWWW-Authenticate);blockedmeans anti-bot protection was reliably identified via a small conservative signal list (NessGate identifies walls, never evades them); a bare 403 isunknown— auth systems and bot-walls both use it, and NessGate never assumes either.okmeans the endpoint answered a safe request — never that operations succeed or that you are authorized.deadlineMs— a global wall-clock budget for the whole resolution. Past it no new fetch starts; an empty, cut-short result is labeledoutcome: "incomplete"withtruncated: true— never a confident absence. A transient DNS failure on the apex is retried once before it can count as nonexistence.org: true— when the exact host publishes nothing, probe a bounded set of plausible same-registrable hosts (homepage-linked developer/doc subdomains first, thendocs./developers./cloud./api.) forllms.txt,ard.jsonandopenapi.json— the library counterpart of the hosted/explore?org=1(≤4 hosts × 3 paths). Hosts attempted are reported inorgChecked; findings classify normally.
Probed OpenAPI locations include /openapi.yaml and /openapi.yml: YAML specs are
detected via the document's own top-level openapi: marker and returned as pointer
records (verbatim title, no capability enumeration — the library carries no YAML
parser). The llms-full.txt companion location is probed after llms.txt. One
document declared through several channels (well-known + rel="ard" + robots
Agentmap) yields one record, not three.
Optional GB/Z 185.5 discovery (Node only): pass
opts.gbz = { gatewayUrl, fetch, query, headers } to query a gateway you configure with
your own authenticated fetch (mTLS/OIDC stays on your side — the library embeds no
credentials). Returned agent descriptions carry provenance: "gbz-185-5-gateway". Nothing is
auto-discovered; without opts.gbz no gateway is ever contacted.
Also exported: normalizeResources, classifyResource, extractOpenApiCapabilities,
normalizeDomain, validateProbeContent, probeShapeOk, parseLinkRel,
parseAgentmap, parseAidRecord, isAcs, normalizeAcsGatewayResponse, and the
ADAPTERS table.
In a browser
The code runs in browsers, but a browser can only fetch other domains that send CORS
headers — and most .well-known files don't — so browser code resolving arbitrary
domains should use the hosted endpoint instead
(GET https://nessgate.com/discover/{domain}, open CORS, no auth). Resolving your own
domain from your own pages works directly.
Security
This library fetches the domain you pass it and follows redirects. In Node or other server-side environments, a caller that passes an untrusted domain must validate it first: reject private, loopback, link-local, and other reserved addresses, and keep redirects on the original host. Without these checks, an untrusted input could be used to reach internal network resources (SSRF). The hosted resolver at nessgate.com performs these checks (DoH pre-check against private/reserved IPs, on-domain redirects only, size and timeout caps, HTTPS-only); when you run this library yourself, that responsibility is yours.
Specification
This is the reference client for the NessGate Resolver Specification — see the specification and the Charter. The specification is open and independently implementable.
License
Apache-2.0.
