Skip to main content

agentspec

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 fetch that sets the apikey header, and capture that. The easiest way: export the session as a HAR file and use capture --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-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.

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-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 abcBearer <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.

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.py for 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

agentspec-0.3.1.tar.gz (336.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

agentspec-0.3.1-py3-none-any.whl (229.2 kB view details)

Uploaded Python 3

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

Hashes for agentspec-0.3.1.tar.gz
Algorithm Hash digest
SHA256 35496fde75d6fc2b7e4d0677ffb1389b1737902dad8110c0b22803acd2e418e4
MD5 56e2b64011c74ec0835edab4259a2ac1
BLAKE2b-256 6437d78f235c029520d44d8c306b88e75979aa19a37a869417a1eec424cade6c

See more details on using hashes here.

Provenance

The following attestation bundles were made for agentspec-0.3.1.tar.gz:

Publisher: release.yml on huwhitememes/agentspec

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for agentspec-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 560cb73346129583a293c469a8949635fe7084c0d86ccc48f59a7d3f59aac758
MD5 54b293913494f0ff28b11bf088af44c0
BLAKE2b-256 0f420ca0ee540e796d51de1b7b9648adb4ba1a40b9834c11fcad85464a509150

See more details on using hashes here.

Provenance

The following attestation bundles were made for agentspec-0.3.1-py3-none-any.whl:

Publisher: release.yml on huwhitememes/agentspec

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

2.1.1

2 files

2.1.0

2 files

2.0.1

2 files

2.0.0

2 files

1.0.0

2 files

This release

0.3.1 This release

2 files

0.3.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page