Thanks for your interest in contributing! f0_sectools is part of the F0RT1KA / ProjectAchilles ecosystem. Please read this guide and CLAUDE.md — the latter contains the architectural rules every contribution must follow.
These mirror the Critical Rules in CLAUDE.md:
- Read-only by default. New platform tools are read-only. State-changing
actions are gated behind a config flag and a human confirmation token,
routed through
core/gating/, and audited. - Secrets never leave the host or reach the model. Use per-platform
.envfiles (gitignored). Never log, return, or prompt-inject credentials. - Return the findings schema. Every tool emits the normalized schema from
core/schema/. No ad-hoc text output. - Safety logic lives in
core/. Do not re-implement redaction, auth, schema, or gating inside a server. - Design tools for small models. Flat args, short closed enums, few tools per server, bounded/paginated output. See the "Designing Tools for Small Models" section in CLAUDE.md.
The nine built servers follow an identical pattern; core/ does not
change. Do it in this order, TDD-ing each code step. (Background:
architecture.)
- Config — add
<Platform>Config(dataclass +from_env(prefix=...), required vars, optionalverify_tls/allow_write, secrets never logged) tocore/auth/config.py, with a test incore/tests/test_config.py. - Scaffold
servers/<platform>-mcp/:pyproject.toml(depsf0-sectools-core,mcp, + the platform's client lib),README.md(followservers/_TEMPLATE.md),.env.<platform>.example(document the exact required permissions/scopes),f0_<platform>_mcp/__init__.py,tests/. Thenuv sync --all-packages. - Client (
client.py) — thin wrapper exposing only the read methods needed. Asynchttpxfor REST (static Bearer or OAuth); for a synchronous vendor SDK, wrap it and run tools viaasyncio.to_thread. - Errors (
errors.py) —map_<platform>_error(...): auth → posture finding,403→Finding.permission_missing,429→Finding.rate_limited, gateway502/503/504→ "API unavailable". Every failure becomes a finding, never an exception. - Tools (
tools.py) — ≤ ~8 flat read tools returninglist[Finding]; write the contract tests first (fake client) — live data validates real field names later. - Server (
server.py) —MCPServer, one@mcp.tool()per tool with@guarded_tool("<source>")directly beneath it, build the client from config, redact at the boundary (redact_finding(f).model_dump()). The guard is not optional: it turns any error your mapper did not claim into one redacted finding instead of a raw exception string, and a test (core/tests/test_tool_boundary.py) fails if a tool is missing it. - Evals —
evals/<platform>/tasks.yaml(≥1 task per tool) + add the server toSERVERSinevals/test_eval_coverage.pyandSERVER_MODULESinevals/run.py. - Smoke script —
scripts/live_smoke_<platform>.py. - Live-test — create
.env.<platform>at the repo root (gitignored), run the smoke script, and fix-forward field-name/shape mismatches (this step always finds 1–3 — mocks encode assumptions; the live API is truth). Mark live-validated once clean. - Skills — three
SKILL.mdunderskills/<platform>/(a posture/coverage skill, a gap/investigation skill, a platform-native one). Pick a default focus and say so. - Docs & wiring — regenerate the reference
(
uv run python scripts/gen_docs.py— CI fails if stale), then update the platform table in CLAUDE.md, the README status, the user-guide support matrix, and the runtime templates inintegrations/(drift-guarded byintegrations/test_integrations_valid.py). - Verify —
uv run pytest,uv run ruff check .,uv run mypy ., no real.envstaged, conventional commit. If you changed any tool, tool docstring/signature, or skill (not just when adding a platform), rerunuv run python scripts/gen_docs.pyand commit the regenerateddocs/reference/— the CI drift guard (scripts/tests/test_gen_docs.py) fails on stale reference docs.
Gated write actions (only where operationally worth the risk) route through
core/gating/ — never a hand-rolled confirmation. Study
projectachilles-actions-mcp as the reference consumer.
- Language: Python 3.11+. Tooling:
uv,pytest,ruff. - Commits: Conventional Commits (e.g.
feat(tenable): add alert query tool). - Tests must pass (
pytest) and code must lint (ruff check) before a PR. - Stage specific files; never
git add -A(avoid committing.env*or local data).
- Contract tests are mandatory for every tool (mocked APIs, deterministic).
- The small-model tool-calling eval is built alongside every server and expected for new tools. If a tool passes contract tests but a small model cannot reliably call it, the tool's design is wrong — simplify the schema.
By contributing, you agree your contributions are licensed under the Apache License 2.0, consistent with the rest of the project.