Observed since 20 Oct, 23:27 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Turn website traffic into OpenAPI specs and MCP servers for AI agents
Capture website HTTP traffic, discover API endpoints, and produce OpenAPI 3.1 specs + MCP servers for AI agents.
Fully deterministic. No LLM calls. No heuristics requiring judgment.
pip install -e . # core CLI + spec generation (typer is bundled - no extra needed)
# or, for development:
pip install -e ".[dev]"
Optional extras:
pip install -e ".[capture]" # Playwright browser capture
pip install -e ".[capture-stealth]" # Stealth Chromium (Patchright) - for Google-SSO / anti-bot sites (see `capture --stealth`)
pip install -e ".[generate]" # MCP server generation deps (adds the `agentspec-mcp` command)
pip install -e ".[yaml]" # YAML output
pip install -e ".[test]" # `agentspec test` (schemathesis-backed API testing)
pip install -e ".[graphql]" # GraphQL SDL inference (graphql-core)
pip install -e ".[all]" # Everything
agentspec discover --trace ./trace/ --out ./output/
agentspec capture --url https://example.com --out ./output/
Sites that block automated browsers (Google sign-in's "This browser or app
may not be secure", anti-bot firewalls): add --stealth to swap in Patchright
(a drop-in stealth Chromium). Needs the optional extra:
pip install -e ".[capture-stealth]" && patchright install chromium
agentspec capture --url https://app.example.com --stealth --out ./output/
API-key-authenticated APIs (Supabase, Firebase, etc.): capture a client of
the API, not the vendor's admin dashboard. The dashboard authenticates with your
login session (Bearer) against the management API, so the service's apikey:
header - which lives on the project's client API and is what agentspec detects
fetch that
sets the apikey header, and capture that. The easiest way: export the
session as a HAR file and use capture --har (below).Browser captures can't see API-key headers - browsers never send apikey:/
X-API-Key: (see the note above). A HAR 1.2 file exported from Postman,
Insomnia, Bruno, Charles, Fiddler, mitmproxy, or Chrome/Firefox DevTools
carries the FULL request headers the client actually sent. Translate it directly
into agentspec's trace format and run the same discover pipeline:
agentspec capture --har ./traffic.har --out ./output/
No optional extra needed (stdlib JSON only). Credentials are redacted on ingest
--probe-auth works against the live origin afterward
(HARs carry real URLs).Multi-origin HARs (a real client session hits your API + CDNs + analytics):
filter with the same flags discover takes:
agentspec capture --har ./traffic.har --out ./output/ \
--origin https://api.example.com --exclude "cdn\.|analytics\."
Stateful validation (agentspec test --stateful): a CRUD-shaped HAR
(POST/GET/PATCH/DELETE on /items/{id}) produces a linkable spec - browser
captures lack REST CRUD chains and hit NoLinksFound. Export a real client
session as HAR, run capture --har, then agentspec test --stateful against
the emitted spec. A captured HAR is thinner than a hand-authored spec, so
--stateful will surface spec-completeness findings (undeclared 4xx responses;
schema-violating requests a lenient server accepts) - point it at a
schema-enforcing target, or treat the findings as useful spec-gap signal.
agentspec generate mcp --spec output/openapi.json --out mcp_server.py
Auth headers are wired by name only - the generated server reads each secret from
an AGENTSPEC_AUTH_<NAME> environment variable at runtime, so secrets are never written
into the generated source or passed on the agentspec command line:
agentspec generate mcp --spec output/openapi.json --auth-header Authorization --out mcp_server.py
At server runtime, set the env var (header name upper-cased, non-alphanumerics → _).
To keep the secret out of shell history, use read -s or a secret manager rather than
a literal export '...':
read -rs AGENTSPEC_AUTH_AUTHORIZATION # type the bearer token (no echo), press Enter
export AGENTSPEC_AUTH_AUTHORIZATION
python mcp_server.py
agentspec test generates schemathesis
property-based tests from an emitted spec and runs them against the live API.
It needs the optional [test] extra (schemathesis pulls a heavy transitive graph -
hypothesis, jsonschema-rs, pytest>=9 - which is why it is opt-in):
pip install -e ".[test]"
agentspec test --spec output/openapi.json
The target server defaults to the spec's servers[0].url; override with --origin.
Auth uses the same name-only convention as generate mcp - secrets come from
AGENTSPEC_AUTH_<NAME> env vars at test-collection time. Extra args after -- go to
pytest:
agentspec test --spec output/openapi.json \
--origin https://staging.example.com \
--auth-header Authorization \
-- -x --maxfail=5
The exit code is pytest's (0 = clean, 1 = findings, 2+ = error), so agentspec test
drops straight into CI. Pass --keep-dir PATH to inspect the generated test module.
--stateful)The default mode runs stateless tests - one property-based test per operation.
Add --stateful to instead run stateful tests that chain operations via
OpenAPI links (create → read → update → delete → confirm-gone), surfacing
state-dependent bugs the stateless mode structurally cannot. Schemathesis's
dependency inference infers the links from the spec's shape (path params +
response fields), so a captured CRUD spec needs no hand-written links.
agentspec test --spec output/openapi.json --stateful
This is destructive. Stateful testing actively creates / modifies / deletes
resources on the target as it explores state - more dangerous even than
--coverage. Only point it at a disposable / staging / localhost target you
own. It errors (NoLinksFound) on specs with no linkable producer/consumer
operations - e.g. a browser capture with no REST CRUD chains; an API-client
capture (a real client session, not a browser) is the input shape that produces
linkable specs.
securitySchemes detectionWhen the captured session includes an OAuth authorization leg (a GET to an
/oauth* / /openid-connect endpoint carrying response_type or redirect_uri),
agentspec discover emits an OpenAPI securitySchemes.OAuth2 entry and attaches
a security requirement to the operations that actually carried a Bearer token.
Coverage and honesty notes:
flows.x-observed-authorization-code
extension. The standard authorizationCode flow is intentionally NOT emitted
because its required tokenUrl is not observable from the browser-redirect leg
(inventing one would be dishonest); the extension documents what was seen. Fill
tokenUrl from .well-known/oauth-authorization-server if you need a strict-valid
standard flow. implicit legs (where tokenUrl is not required) use the
standard flows.implicit object.security is attached per-operation (only on Bearer-bearing ops), never
top-level, so public endpoints are not falsely marked OAuth-mandatory. An
endpoint is marked mandatory only when every successful sample carried Bearer
(a 401-then-Bearer retry sequence still qualifies).client_id, state, and redirect_uri values never reach the spec - only
the authorizationUrl (query/matrix-params/userinfo stripped), scope names,
and grant type. The capture redactor preserves just the auth scheme
(Authorization: Bearer abc → Bearer <redacted>) so detection works on real
captured traces without persisting the credential.--exclude is honored (an excluded IdP URL contributes nothing); --origins
/ --include do not filter the scan, because the IdP authorization leg is
inherently cross-origin from the protected API.The redirect_uri bypass vulnerability fuzzer deferred from this detection
milestone is now shipped as agentspec audit oauth - see the next section.
redirect_uri bypassesagentspec audit oauth is an ACTIVE vulnerability fuzzer: it takes an OAuth
authorization URL, tampers the redirect_uri across a 27-scenario parser-confusion
taxonomy (evil-domain substitution, @ username tricks, // no-scheme,
%2f/%5c/%23 domain bypasses, IDN homograph, scheme/IPv6 tricks, ...), fires
each at the IdP's /authorize endpoint, and classifies the first-hop response as
VULNERABLE / BLOCKED / AMBIGUOUS. It tests the IdP's redirect_uri
validator - the "evil" host never needs to exist.
pip install -e ".[audit]" # httpx (reuses [probe])
# from a full authorization URL:
agentspec audit oauth --i-am-authorized \
--authz-url 'https://idp.example.com/oauth/authorize?response_type=code&client_id=...&redirect_uri=...' \
--out ./audit-report
# or extract the authorization URL from a captured CDP trace:
agentspec audit oauth --i-am-authorized --from-trace ./trace/ --origin https://idp.example.com --out ./audit-report
Authorization gate (load-bearing): this is an active attack tool - it sends
crafted bypass attempts at a live OAuth server. --i-am-authorized is required
(every run prints the disclaimer); only run it against systems you own or are
authorized to assess. Unauthorized use may violate the law (e.g. CFAA) and the
target's terms. The operator bears responsibility; the tool warns and gets out of
the way - no target restriction beyond the flag.
The verdict is structural, not prose: VULNERABLE iff the IdP would hand the
browser to the attacker host (3xx Location → attacker, or a 200 body that
auto-navigates / auto-submits a form to it); BLOCKED iff 4xx or an OAuth
error=redirect_uri_mismatch; else AMBIGUOUS. The JSON + Markdown report is
credential-redacted at the write boundary (code/state/access_token values
never persist). Validated against real IdPs (Keycloak, Auth0) - both correctly
reject all 26 bypasses (0 VULNERABLE).
CDP Capture → load → filter → normalize → infer → emit
pair drop templatize schema openapi.yaml
req/resp noise paths, from openapi.json
bodies by GraphQL/ samples report.md
domain RPC redact confidence.json
PII mcp_server.py
5 stages, fully offline. Each stage is a pure function you can call independently:
from agentspec.pipeline.load import load_from_dir
from agentspec.pipeline.filter import filter_pairs
from agentspec.pipeline.normalize import normalize
from agentspec.pipeline.infer import infer_schemas
from agentspec.pipeline.emit import emit
| File | Description |
|---|---|
openapi.yaml | OpenAPI 3.1 spec with component hoisting |
openapi.json | Same spec in JSON |
confidence.json | Per-endpoint confidence scores (low/medium/high) |
report.md | Human-readable summary with curl examples |
pytest # 529 tests (run `pytest tests/` to see the current count)
pytest -v # verbose
pytest --cov # coverage
redact.py for the full scope)MIT. See LICENSE and ATTRIBUTION.md.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.3.1 | 31 Jul 2026 | 14 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 23:27 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:c9c92e88e6be24daf1a61ab308731d4c9a019f63760c1d7f8e1e1d3c4fa1ec99