agentspec
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.
Install
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
Usage
Discover from a CDP trace
agentspec discover --trace ./trace/ --out ./output/
Capture + discover in one step
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
- is never sent by the browser. agentspec can only see auth the browser actually
sends, so drive a real client app, the in-product API explorer, or a
fetchthat sets theapikeyheader, and capture that. The easiest way: export the session as a HAR file and usecapture --har(below).
Capture from a HAR file (Postman, Insomnia, mitmproxy, curl)
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
- they never reach disk.
--probe-authworks 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.
Generate an MCP server
agentspec generate mcp --spec output/openapi.json --out mcp_server.py
Generate with auth headers
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
Test an API against its spec
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 testing (--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.
OAuth securitySchemes detection
When 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:
- authorizationCode legs are recorded under a
flows.x-observed-authorization-codeextension. The standardauthorizationCodeflow is intentionally NOT emitted because its requiredtokenUrlis not observable from the browser-redirect leg (inventing one would be dishonest); the extension documents what was seen. FilltokenUrlfrom.well-known/oauth-authorization-serverif you need a strict-valid standard flow. implicit legs (wheretokenUrlis not required) use the standardflows.implicitobject. securityis 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, andredirect_urivalues never reach the spec - only theauthorizationUrl(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.--excludeis honored (an excluded IdP URL contributes nothing);--origins/--includedo 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.
Audit OAuth redirect_uri bypasses
agentspec 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).
Pipeline
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
Output
| 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 |
Development
pytest # 529 tests (run `pytest tests/` to see the current count)
pytest -v # verbose
pytest --cov # coverage
Architecture
- schema/ - Associative JSON Schema merge, path templating, pattern-based PII redaction (identifiers like email/phone/card/SSN/IBAN; not free-text names/addresses - see
redact.pyfor the full scope) - pipeline/ - 5-stage discover pipeline (load, filter, normalize, infer, emit)
- generators/ - MCP server code generation
- capture/ - CDP traffic capture via Playwright
- cli.py - Typer CLI
License
MIT. See LICENSE and ATTRIBUTION.md.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agentspec-0.3.1.tar.gz.
File metadata
- Download URL: agentspec-0.3.1.tar.gz
- Upload date:
- Size: 336.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
35496fde75d6fc2b7e4d0677ffb1389b1737902dad8110c0b22803acd2e418e4
|
|
| MD5 |
56e2b64011c74ec0835edab4259a2ac1
|
|
| BLAKE2b-256 |
6437d78f235c029520d44d8c306b88e75979aa19a37a869417a1eec424cade6c
|
Provenance
The following attestation bundles were made for agentspec-0.3.1.tar.gz:
Publisher:
release.yml on huwhitememes/agentspec
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agentspec-0.3.1.tar.gz -
Subject digest:
35496fde75d6fc2b7e4d0677ffb1389b1737902dad8110c0b22803acd2e418e4 - Sigstore transparency entry: 2303693618
- Sigstore integration time:
-
Permalink:
huwhitememes/agentspec@9a40dfe586bb16a9b7d50141c7b08bde2e623980 -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/huwhitememes
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9a40dfe586bb16a9b7d50141c7b08bde2e623980 -
Trigger Event:
push
-
Statement type:
File details
Details for the file agentspec-0.3.1-py3-none-any.whl.
File metadata
- Download URL: agentspec-0.3.1-py3-none-any.whl
- Upload date:
- Size: 229.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
560cb73346129583a293c469a8949635fe7084c0d86ccc48f59a7d3f59aac758
|
|
| MD5 |
54b293913494f0ff28b11bf088af44c0
|
|
| BLAKE2b-256 |
0f420ca0ee540e796d51de1b7b9648adb4ba1a40b9834c11fcad85464a509150
|
Provenance
The following attestation bundles were made for agentspec-0.3.1-py3-none-any.whl:
Publisher:
release.yml on huwhitememes/agentspec
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agentspec-0.3.1-py3-none-any.whl -
Subject digest:
560cb73346129583a293c469a8949635fe7084c0d86ccc48f59a7d3f59aac758 - Sigstore transparency entry: 2303693658
- Sigstore integration time:
-
Permalink:
huwhitememes/agentspec@9a40dfe586bb16a9b7d50141c7b08bde2e623980 -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/huwhitememes
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9a40dfe586bb16a9b7d50141c7b08bde2e623980 -
Trigger Event:
push
-
Statement type: