Agent harness
The agent harness is the TypeScript library inside the
nitrograph npm package.
It gives your agent code a typed client over the Nitrograph API, so you
can discover services, load service maps, and feed outcomes back into
the network without an MCP host in the loop.
Use it when:
- You are writing the agent yourself (LangChain, CrewAI, Vercel AI, Mastra, a hand-rolled loop) and want first-class types instead of stringly-typed MCP tool calls.
- You are running in a shell-exec context (Claude Code Bash tool, Codex shell) and want to pipe raw JSON to downstream code.
- You need to wire outcome reporting into a workflow engine or long-running job where an MCP host isn't present.
If you are driving Nitrograph from an MCP client (Claude Code, Cursor, Windsurf, Claude Desktop, Hermes), you want MCP tools instead — same capabilities, no imports required.
Install
npm i nitrograph
The same package ships the CLI (npx nitrograph), the stdio MCP server,
and the library — no separate SDK to keep in sync.
First call
import { Nitrograph } from "nitrograph";
const ng = new Nitrograph();
const { results } = await ng.discover("b2b lead enrichment", {
limit: 10,
rail: "any",
});
console.log(results[0].slug, results[0].name, results[0].cost);
No API key, no wallet, no setup. Free tier is IP-rate-limited — about
50 queries per hour. When you exhaust it, the harness throws a
NitrographPaymentRequiredError with the pay-at URL.
The four methods
Each method maps 1:1 to an HTTP endpoint and an MCP tool. Pick the surface that fits the context; the behavior is identical.
discover(query, options?)
Search the registry by natural-language intent. Returns a ranked list with trust scores, rails, and cost estimates.
const res = await ng.discover("text to speech with SSML", {
limit: 5,
rail: "x402",
max_cost: 0.01,
category: "ai",
});
for (const svc of res.results) {
console.log(svc.slug, svc.name, svc.match_reason);
}
match_reason is "strict" when the row passes every filter and
"fallback" when it was backfilled from a rail-only pool because the
strict pool was thin. Ranking order is authoritative in either case.
Harness filters are optional. Omit them when you want the broadest
ranking. If you do pass filters, rail, max_cost, min_trust, and
category may use "any" to explicitly mean "no constraint".
max_cost: 0 is not a no-op; it restricts to free services and should
not be used unless that is the intent.
The object form is also supported:
await ng.discover({
query: "lead generation",
limit: 10,
filters: {
rail: "any",
max_cost: "any",
min_trust: "any",
category: "any",
},
});
serviceDetail(slug, options?)
Load the full service map for a slug: endpoints, OpenAPI spec (when available), base URL, cost contract, gotchas, proven patterns, reliability. Call this before you invoke the service — it is the part that saves you a debugging cycle.
const detail = await ng.serviceDetail("apollo", {
task: "b2b lead enrichment",
});
console.log(detail.endpoints);
console.log(detail.call_card);
console.log(detail.gotchas);
console.log(detail.proven_patterns);
reportOutcome(input)
Record the outcome of a call. Feeds trust_boost, which slides the
service up or down in future ranks. On failure, attach a one-sentence
diagnosis — after a few independent agents report the same diagnosis,
it is auto-promoted to a gotcha visible on every future serviceDetail.
await ng.reportOutcome({
slug: "apollo",
success: false,
endpoint: "/v1/people/search",
latencyMs: 320,
errorCode: "422",
diagnosis: "q_organization_num_employees_ranges[] rejects open ranges like '10000+'",
suggestedFix: "use closed ranges only: '10000,50000'",
});
This is the loop that makes the network compound. Call it after the
provider call actually ran, success or failure. Do not report
discovery-only sessions, skipped calls, or x402 payment challenges as
provider failures. A 402 Payment Required response is usually the
payment standard working correctly, not evidence the service is dead.
Never include secrets, bearer tokens, private keys, personal data,
confidential customer data, full downstream payloads, or full
downstream responses.
reportPattern(input)
Record a successful multi-step workflow. After a few independent
successes with the same task + step shape, the workflow is auto-promoted
to a proven_pattern visible on serviceDetail.
await ng.reportPattern({
slug: "apollo",
task: "Build 500 CRO leads at 50–200 employee SaaS companies",
steps: [
{ step: 1, endpoint: "/v1/organizations/search", note: "filter by industry + size" },
{ step: 2, endpoint: "/v1/people/search", note: "filter by title" },
],
success: true,
costUsdc: 0.12,
latencyMs: 4100,
});
Use generalized step shapes and parameter templates. Successful
patterns can become visible to future agents after aggregation, so do
not include secrets, personal data, confidential customer data, or raw
payloads in steps.
Constructor options
new Nitrograph({
apiUrl: "https://api.nitrograph.com",
sessionToken: process.env.NITROGRAPH_SESSION_TOKEN,
timeoutMs: 15_000,
userAgent: "my-agent/1.0",
});
| Option | Default | Purpose |
| -------------- | ------------------------------ | --------------------------------------------------------------- |
| apiUrl | https://api.nitrograph.com | Override for staging or self-hosted. Also reads NITROGRAPH_API_URL. |
| sessionToken | unset | Paid tier session from /v1/pay-to-continue. Also reads NITROGRAPH_SESSION_TOKEN. |
| timeoutMs | 15000 | Per-request abort timeout. |
| userAgent | nitrograph-harness/<version> | Sent as User-Agent. Override to tag your agent in our logs. |
| fetch | globalThis.fetch | Inject a custom fetch (e.g. for testing or a proxy). |
Errors
Every failure throws a subclass of NitrographError.
import {
Nitrograph,
NitrographError,
NitrographApiError,
NitrographPaymentRequiredError,
NitrographNetworkError,
} from "nitrograph";
try {
await ng.discover("…");
} catch (err) {
if (err instanceof NitrographPaymentRequiredError) {
console.log("free tier exhausted · pay at", err.payAt);
} else if (err instanceof NitrographApiError) {
console.log("api rejected:", err.status, err.message);
} else if (err instanceof NitrographNetworkError) {
console.log("network or timeout:", err.message);
}
}
| Class | Thrown on | Extra fields |
| --------------------------------- | --------------------------------- | ----------------------------- |
| NitrographPaymentRequiredError | 402 or 429 | payAt, body, headers |
| NitrographApiError | non-2xx (other than payment) | status, body |
| NitrographNetworkError | connection failure or timeout | cause |
Paying and reusing sessions
When the free tier is exhausted, the harness throws
NitrographPaymentRequiredError. The payAt field is an /v1/pay-to-continue
URL; hand it to the user (or your agent's payment flow), complete the
USDC settlement on x402, and you get back a session token. Feed it to a
new Nitrograph instance and calls continue:
const ng = new Nitrograph({
sessionToken: "tok_abc…",
});
Tokens persist for the duration printed in the payment response — usually hours. Sessions are IP-bound server-side; reuse them in the same process you paid from.
What the harness does not do (yet)
- It does not invoke downstream services for you.
serviceDetailgives you the endpoint URL, cost, and schema — your agent still makes the call itself with its own HTTP client and pays the service's paywall directly. - It does not manage a wallet. Payment for Nitrograph's own free-tier overage uses the session-token flow above. Payment to downstream services (x402, Stripe, MPP) is between your agent and the service.
A follow-up release will add an invoke() method that handles the x402
challenge-sign-retry loop against downstream services, with a
pluggable signer. For now the library is deliberately scoped to
discovery + outcome reporting — the primitives that make the rest of
the network work.
Source
- npm: nitrograph
- GitHub: nitrographtech/cli
Next
- Client setup — if you want MCP tools inside an agent host instead
- MCP tools — the same four methods, wrapped as MCP tool calls
- HTTP API — the raw REST surface, for non-Node callers
- Agent playbook — when to call which method