Skip to main content
Glama
luiacuaniello

PerspectiveGraph

PerspectiveGraph joins what you already run - Trivy, Semgrep, Cloud Custodian, Falco, plus your AWS and Kubernetes state - into one graph of your real environment, and asks a single question of it: can someone get from the internet, through privilege that is too broad, to something worth stealing?

On a pull request it asks that question before the merge: the check goes red only when this change opens a route, and the fix comes back as its own pull request. Open source (Apache 2.0), runs on your infrastructure, collects no telemetry.

PerspectiveGraph: from the day's exploitable routes to a generated fix

Twelve seconds of make demo: what is exploitable now → the ranked routes → one route's kill chain and the fix it generates → whether the scores can be trusted. Sample scanner output and seeded verdicts, not a real environment.

A score here is what the model concludes from the evidence it was given, not a measured frequency: nothing has been calibrated against field data yet, and the engine says so itself rather than rounding up. What is measured, and what is not.

Check your own account in 30 seconds

No deployment, no Docker, nothing ingested. One static binary asks AWS's own policy evaluator which of your roles can reach administrator - applying the service control policies, permission boundaries and condition keys that a policy reader on its own does not see:

# macOS (Apple silicon); swap darwin_arm64 for linux_amd64, linux_arm64 or darwin_amd64
curl -sSL https://github.com/luiacuaniello/perspectivegraph/releases/latest/download/perspectivegraph_darwin_arm64.tar.gz | tar xz
./perspectivegraph redteam -roles -region eu-west-1

It is read-only and free: each check is one iam:SimulatePrincipalPolicy dry run, which creates nothing and costs nothing, and the only permissions it needs are that and iam:ListRoles, both inside SecurityAudit. Windows builds are on the releases page; every binary is signed with cosign and carries SLSA provenance, and two commands verify both before you run anything.

Add -compare and it also runs the engine over the same account, exiting non-zero where the two disagree. That is how the engine's first real false positive was found, and how it stays fixed. If it disagrees on yours, report it: no report is more useful. From here, how to evaluate this walks to a verdict on your own estate in stages that each end in an answer.

Related MCP server: cloud-pathfinder

See the whole engine in 90 seconds

make demo

Pulls the published, cosign-signed images, feeds them sample Trivy / Semgrep / Custodian / Falco / Kubernetes / IAM / SSO output, and prints the top attack path with its generated fix. Dashboard on http://localhost:3000, make down to tear it down. It needs Docker, jq and curl, compiles nothing, and takes about 23 seconds from an empty image cache; make demo-build is the same demo built from your working tree.

On Kubernetes, the chart is an official package on Artifact Hub:

helm install perspectivegraph oci://ghcr.io/luiacuaniello/charts/perspectivegraph \
  --version 1.39.1 # x-release-please-version

On one machine - a VM with Docker and a DNS name - one command sets up TLS, generated credentials, an authenticated bus and daily backups, and starts it: production on one machine.

make prod-init DOMAIN=perspectivegraph.example.com

The chart and the three images it runs (ghcr.io/luiacuaniello/perspectivegraph, -dashboard and -postgres) are signed with cosign keyless and carry an SPDX SBOM and SLSA provenance: verify them rather than taking the supply chain on trust.

The dashboard opens on the decision, not the inventory: what is being exploited now, the fewest changes that remove the most risk, and how far the numbers can be trusted. Routes are ranked by triage priority - what the route reaches, whether runtime confirmed it, how exposed the entry is - so a lower-scoring route can outrank a higher-scoring one.

The day's decision surface

Attack path detail

Score accuracy

Why the route is P1, one probability with its range, and every hop with what it lets the attacker do and where its probability came from.

Whether the engine's own scores held up against recorded outcomes - and whether there are enough of them to say.

The screenshots are make demo with seeded verdicts: make seed-validation records synthetic outcomes on part of the routes, the way a BAS run tests part of an estate, which is why the calibration panel has something to show - and why it still says not enough outcomes yet: the handful it records is far short of the 30 a verdict needs. The counts in the pictures are those of the release they were taken on; the sample data has grown since, so yours differ by a route or two. A fresh install and the public demo report insufficient data instead, until real outcomes exist. The public demo runs on one free VM, so treat it as best-effort.

Why?

A scanner reports that a container carries a critical CVE. It cannot report that the container sits behind an internet-facing load balancer, runs with a role that reads the production database, and is therefore the one finding out of ten thousand worth fixing this week. That needs the other tools' output in the same graph, which is what this builds. A developer gets a check that goes red only when their change opens a real route; a security team gets a short ranked list of attack paths instead of a flat list of findings.

Block the pull request that opens the path

No deployment required. The runner reads your estate read-only, ingests this pull request's scan, and answers in-process with the same engine:

- uses: luiacuaniello/perspectivegraph@v1
  with:
    mode: local
    aws-region: eu-west-1     # read-only; give the job an OIDC role with SecurityAudit
    report: trivy.json

The check goes red when this change opens a route to a sensitive asset - or makes one likelier - not when it adds a critical CVE: a critical on a host nothing routes to does not fail the build, and a medium on a container that now reaches the production database does. Routes that were there before the change do not count, even when they run through what it touches: the scan is applied to a copy of the estate and compared with it, and nothing is written. It also has a third outcome, because a pipeline whose scan never arrived must not get the same green tick as one that is clean:

Verdict

Exit

Meaning

clean

0

The engine analysed this change: it opens or worsens no critical path

blocked

1

It opens or worsens critical attack paths - the check names them

unknown

2

Nobody analysed it. The scan, the ingest or the SHA is wrong

A Terraform plan goes through the same gate before anything is deployed: source: terraform with the output of terraform show -json, judged on its own or against the live estate. A security group rule, a policy attachment or a bucket policy that opens a route fails the check in the pull request that writes it - see Terraform plans.

Outside GitHub Actions it is one command, and it installs as a Trivy plugin too:

perspectivegraph gate -local -aws-region eu-west-1 -report trivy.json -slug owner/name -sha "$COMMIT_SHA"

trivy plugin install github.com/luiacuaniello/perspectivegraph
trivy image -f json myapp:pr-42 | trivy perspectivegraph gate -local -aws-region eu-west-1 -report -
WARNING

Fork pull requests get no secrets, so the gate fails closed as unknown on them. Do not work around it with pull_request_target: that runs your secrets - in local mode, cloud credentials - against the contributor's code.

On a public repository, a blocked check prints the route (real asset names, the CVE, the sensitive asset) into a public job log. Use soft-fail and post the detail somewhere private.

The manual covers the rest: pointing the action at a deployed engine, gating rendered manifests, a pre-collected estate, rolling the gate out, and every input in action.yml.

Let an agent query it

A language model cannot enumerate thousands of edges reliably or run Dijkstra, and asked for "the attack paths in my account" it will invent plausible ones. So the engine speaks MCP: the agent asks, and reasons over answers it could not have made up.

perspectivegraph mcp --api http://localhost:8080   # or https://demo.a3thinker.it, no credential needed

Eight tools, every one read-only and declared so on the wire. The one worth the integration is simulate_fix: it re-runs the simulation with the given edges cut and reports what actually changes. The server is in the official MCP Registry and on Glama; the tools and the client configuration are in the manual.

PerspectiveGraph MCP server on Glama

Project status & maturity

OpenSSF Best Practices Artifact Hub Go

The engine and its public API are complete, documented and tested. The scores are not yet calibrated. Nobody has run this over a real estate, tested the paths it surfaced and fed the verdicts back: the machinery for that loop is built and tested, the loop is not closed. So read a score as what the model believes and how sure it says it is, not as a measured frequency. Use it to find and cut routes; don't put its percentage in front of a board. If you run it over yours, tell us how it went. Positioning spells out what is and isn't claimed.

What is measured today, as of v1.39.1. make bench-cloudgoat grades the engine in CI on four CloudGoat-shaped scenarios:

Scenario

Expects

Result

ec2_ssrf

a path

found it, invented none

iam_privesc_by_attachment

a path (leaked-credential origin)

found it, invented none

ec2_private_subnet_no_path

no path (open SG, private subnet)

produced none

iam_privesc_denied_by_guardrail

no path (explicit Deny wins)

produced none

Precision and recall are 1.00 on all four: four known shapes, two of them negative controls, so a regression gate rather than a measure of accuracy on your estate. On real AWS, make reachability-lab-aws checks exposure the same way for free, and make redteam-aws grades the engine's escalation claims against AWS's own policy evaluator. That grading has already caught one false positive, a permissions boundary the engine ignored; it is fixed, and make boundary-lab-aws fails whenever the engine and AWS disagree. make entrypoints-lab-aws puts bucket policies and function URLs to AWS the same way; its first run caught four errors, also fixed. make public-access-lab-aws does it, for free, for Block Public Access set on the whole account, and make terraform-lab-aws applies the Terraform plans the merge gate judged and asks the internet whether each one opened a way in. Each of these labs writes what it found into the build, and the dashboard's Accuracy page shows it under Checked against AWS: every question, AWS's answer, the engine's, and the version it was asked of.

  • Clouds. AWS is live and verified against a real account; reading several accounts in one pass is exercised on fixtures, not yet against real ones. Azure is fixtures only, and there is no connector for GCP or for OpenStack: the roadmap says what is planned for each.

  • Interface. The GraphQL schema is frozen and drift-guarded, so a breaking change comes with a major version, never in a patch: API stability.

  • Deployment. The backend is distroless and non-root on a read-only root filesystem, the images are pinned by digest, and under Helm every workload meets the restricted Pod Security Standard, asserted in CI. The compose defaults are open on purpose; PG_ENV=production refuses to start unless API and ingest are authenticated. A real rollout needs more (TLS, backups, TRUSTED_PROXY_CIDRS behind a proxy, a database you can fail over): on one machine make prod-init does the first three, and CI restores one of its backups on every change; the operations runbook lists the rest.

  • Support. The newest release only, with a clock on security fixes (Critical 7 days, High 30): SUPPORT.md.

  • Telemetry. None. Out of the box it opens no outbound connection; GitHub, the AI assistant and the KEV/EPSS feeds each stay off until you configure them.

  • Scope. The reachable attack-path question, in the developer workflow. It is not a scanner, a CNAPP or a compliance product, and replaces none of them.

  • How it is written. By a human working with Claude (Anthropic): the design decisions and what ships are the maintainer's, and much of the implementation and its tests came out of that collaboration. Check it rather than trust it: make test, make bench-cloudgoat, govulncheck ./..., and CONTRIBUTING.

Documentation

License

Apache License 2.0. Licensing is declared for every file, in the form REUSE defines, and checked in CI: REUSE.toml.

Available Tools

8 tools
explain_attack_pathA
Read-onlyIdempotent

Give the full kill chain for one route: every hop, the relationship type, that hop's probability, where the probability came from (kev/epss/runtime are observed evidence; cvss/severity/heuristic are estimates), and the MITRE ATT&CK technique. Use this before explaining or acting on a route - the hop provenance is what tells you which parts of the story are evidence and which are assumption.

NameRequiredDescriptionDefault
path_idYesThe `id` of a route from list_attack_paths. An id lasts as long as its route: one that has since been closed is reported as no longer open, and list_attack_paths gives the current ones.

Output Schema

NameRequiredDescription
idYesThe route's id, as asked for.
nodesNoThe assets on the route, entry point first, target last.
scoreNoEnd-to-end probability that an attacker completes the route, 0-1. A modelled estimate, not a measured rate: see get_score_trust.
stepsNoThe hops, in order. Each is one relationship an attacker crosses.
priorityNoTriage priority, 0-100: the score blended with corroboration, target sensitivity and how many routes share the entry point. This is the order of the list.
confidenceNoHow far the score can be trusted, 0-1: the mean confidence of its hops.
scoreCiLowNoLow end of the 90% credible interval around posteriorMean. A wide interval means the score rests on soft inputs.
suppressedNoTrue when an analyst has triaged the route off the board (accepted risk, false positive). It still exists, but do not present it as open work.
scoreCiHighNoHigh end of the same interval.
mixtureScoreNoThe score averaged over attacker profiles, weighted by how likely each is.
remediationsNoWays to close the route.
posteriorMeanNoThe score with the uncertainty of each hop and the mix of attacker capabilities taken into account, 0-1.
priorityLabelNoPriority band: P1, P2 or P3. P1 is what to fix first.
profileScoresNoThe score against each attacker profile.
correlatedHopsNoTrue when two or more hops rest on the same kind of evidence.
priorityReasonNoOne sentence saying why the route is in its band, when a fact put it there; null when the blended number did.
confidenceLabelNoThe same as a band: high, medium or low.
priorityFactorsNoThe reasons behind the priority, human-readable.
scoreUpperBoundNoThe score if the hops fail or succeed together rather than independently: the weakest hop's probability.
runtimeConfirmedNoTrue when a runtime alert sits on the route.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, non-destructive), so the bar is lower, and the description adds real behavioral context: the distinction between observed evidence sources (kev/epss/runtime) and estimates (cvss/severity/heuristic). It does not mention rate limits, error behavior, or latency, but the provenance semantics are the key non-obvious trait.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences: the first front-loads the returned content, the second front-loads the usage trigger. The dense parenthetical earns its place by disambiguating evidence versus estimate, and there is no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present and 100% parameter coverage, the definition only needs to add decision-relevant framing, which it does via the provenance interpretation guidance. Nothing an agent needs to invoke this correctly or interpret the result is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the single parameter path_id is fully documented in the schema, including the lifetime/closed-route caveat. The description adds nothing about the parameter itself, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('explain_attack_path' / 'give the full kill chain for one route') and enumerates exactly what is returned: every hop, relationship type, hop probability, probability provenance, and MITRE ATT&CK technique. This is clearly distinguishable from sibling list_attack_paths (which only enumerates routes) and routes_to_target.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says when to use it: 'Use this before explaining or acting on a route', establishing it as a prerequisite step over acting directly on a path. It does not name an alternative tool or state when-not to use it, so it falls short of the 5-level routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_postureA
Read-onlyIdempotent

Summarize the environment: how many attack routes are open, how many are runtime-confirmed, how many assets and relationships are mapped, and which sensitive assets an attacker can currently reach. Start here to orient before enumerating anything: it also gives the names the other tools take - the sensitive assets routes_to_target accepts and the applications app arguments accept.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
postureNoCounts for the whole environment.
applicationsNoThe application names this environment knows: the values `app` arguments accept.
riskSimulationNoThe environment's overall risk, simulated over every route at once.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, so the safety profile is covered. The description adds meaningful non-annotation context: that the call returns identifier names consumed by other tools (routes_to_target targets and `app` arguments), which is exactly the kind of cross-tool behavior an agent needs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the concrete summary contents before the orientation guidance. The list-heavy first sentence is dense but every item is informative; no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be re-explained, and the description appropriately covers the conceptual content and the tool's role as the entry point in the workflow. It is essentially complete for a zero-parameter overview tool; only the lack of any explicit exclusion guidance keeps it from a 5.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so the baseline is 4. The description correctly focuses instead on output-derived names that become parameters for sibling tools, which is useful framing though not a parameter concern for this tool itself.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb+resource ("Summarize the environment") with an explicit enumeration of what is summarized: open attack routes, runtime-confirmed routes, assets, relationships, and reachable sensitive assets. It also distinguishes itself from siblings by positioning as the orientation entry point rather than an enumeration tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says "Start here to orient before enumerating anything," which is clear when-to-use guidance relative to the listing siblings. It stops short of naming an alternative or stating when not to use it (e.g., for a single path), so it is clear context without exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_score_trustA
Read-onlyIdempotent

Report how well the engine's probabilities have matched reality, measured against recorded red-team or BAS outcomes: the verdict (well-calibrated / calibrated-on-average / overconfident / underconfident / insufficient-data; calibrated-on-average means only the average matches, so no individual score may be quoted as a probability), the predicted-versus-observed rates, and what to do about the gap. Call this before quoting any score as a probability. If it reports insufficient-data, the numbers are expert estimates and must be presented as the model's own estimate, not as odds. It also reports discrimination - whether the score, and separately the triage Priority order, put confirmed paths above refuted ones (AUC with a 95% interval). Do not present the ranking as evidence of which path is most dangerous unless priorityDiscrimination reads 'discriminates'; 'insufficient-data' or 'indistinguishable-from-chance' means the order has not been shown to beat a coin, and must be said so.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
validationNoWhat happened to the routes that were tested.
calibrationNoThe scores compared with the recorded red-team and BAS outcomes.

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover read-only/idempotent/destructive status, so the safety profile is settled. The description adds substantial behavioral context beyond that: the meaning of each verdict, the calibrated-on-average caveat, and the rule that insufficient-data implies expert estimates. It is rich, though it frames much of this as output interpretation rather than tool behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The primary rule (call before quoting a probability) is front-loaded, and most clauses carry genuinely load-bearing interpretation rules. However, the verdict enumeration is packed into one dense sentence with nested parentheticals, and the ranking caveat is restated, which hurts scannability.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be explained, and the description instead supplies the critical interpretation layer: verdict meanings, discrimination/AUC reporting, and explicit warnings about how to present insufficient or chance-level results. For a no-parameter interpretation tool this is complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With zero parameters and 100% schema coverage, there are no parameter semantics to document; the baseline is 4. The description correctly focuses its words on interpretation rather than inventing parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb+resource: it reports how well the engine's probabilities matched reality against red-team/BAS outcomes, and enumerates the verdicts it returns. This is unmistakably distinct from siblings like get_posture or explain_attack_path, which cover different concerns.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit when-to-use ('Call this before quoting any score as a probability') and conditional handling for downstream states ('If it reports insufficient-data, ... must be presented as the model's own estimate'), plus a when-not-to-use rule for the ranking. Nothing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_attack_pathsA
Read-onlyIdempotent

List the ranked routes from internet exposure to a sensitive asset, highest triage priority first. score is the modelled end-to-end exploit probability and priority (0-100, banded P1/P2/P3) is the triage order that also weighs corroboration and target sensitivity. IMPORTANT: these probabilities are expert estimates, not measurements calibrated against real outcomes - call get_score_trust before presenting any of them as a probability, and prefer the ranking over the absolute values. It returns one page: when nextOffset is not null more routes exist, and passing it as offset returns the next ones. Routes an analyst has suppressed are listed too, marked suppressed.

NameRequiredDescriptionDefault
appNoRestrict to one application, by a name from get_posture's `applications`: a repository slug or the value of a cloud resource's `app` tag, e.g. 'payments-api'. The match is exact, and a name the environment does not know returns nothing. Omit for the whole environment.
limitNoHow many routes to return, priority-first.
offsetNoHow many routes to skip from the top of the ranking: the `nextOffset` of the previous page. Leave it out for the first page.

Output Schema

NameRequiredDescription
offsetYesHow many routes were skipped before this page.
nextOffsetYesPass as `offset` for the next page. Null when this page is the last.
attackPathsYesThe routes, highest priority first.

TDQS

A4.1/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish read-only, idempotent, non-destructive, closed-world behavior, and the description adds substantial context on top: the probabilities are expert estimates rather than calibrated measurements, suppressed routes are still returned but flagged, and results are paginated one page at a time. This is exactly the kind of trust caveat an agent needs before quoting numbers.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and triage ordering, then layers the caveats. Every sentence is informative, though the explanation of score/priority overlaps with what the output schema will already convey, making it slightly denser than strictly necessary.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only ranked-list tool with an output schema, the description covers everything an agent needs: ranking semantics, the trust caveat with a named prerequisite tool, pagination mechanics, and the suppressed-route edge case. Nothing material is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so app, limit, and offset are already fully documented in the schema, making 3 the correct baseline. The description adds a useful link between the returned nextOffset and the offset parameter, but contributes no new per-parameter semantics beyond what the schema states.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource with scope: 'List the ranked routes from internet exposure to a sensitive asset, highest triage priority first.' An agent immediately knows this is a ranked enumeration of attack paths. It does not, however, explicitly distinguish itself from close siblings like explain_attack_path or routes_to_target, both of which sound like they operate over the same routes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete operational guidance: call get_score_trust before presenting the probabilities, prefer the ranking over absolute values, and use nextOffset/offset to page. It also covers the suppression case. It stops short of saying when to choose this tool over routes_to_target or explain_attack_path, so alternative-selection guidance is incomplete.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_fixesA
Read-onlyIdempotent

Return the remediation plan: the fewest changes that remove the most risk, choke points first, each with the share of critical-path risk it eliminates and how many routes it cuts. This is usually the right answer to 'what should we do' - a hundred routes typically collapse into a handful of changes.

NameRequiredDescriptionDefault
appNoRestrict to one application, by a name from get_posture's `applications`: a repository slug or the value of a cloud resource's `app` tag, e.g. 'payments-api'. The match is exact, and a name the environment does not know returns nothing. Omit for the whole environment.

Output Schema

NameRequiredDescription
remediationPlanNoThe changes, the one that removes the most risk first.

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint and destructiveHint=false, so the safety profile is covered. The description adds value beyond them by disclosing output semantics — ranked choke points, per-item risk share and route cuts — which tells the agent what 'fixes' actually contains. It stops short of noting result size or whether the plan is exhaustive versus truncated.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the core verb and resource before the explanatory framing. The closing 'a hundred routes typically collapse into a handful of changes' is helpful intuition but is the least load-bearing sentence and pushes toward verbosity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be explained, and annotations cover the safety profile. The description still supplies the interpretive layer — what ranking means and what each fix reports — leaving only the simulate_fix relationship uncovered. Adequate and nearly complete for a one-parameter read tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Only one parameter with 100% schema description coverage, including exact-match semantics and the 'omit for whole environment' default, so the schema carries this fully. The description contributes nothing about the `app` filter, which is acceptable at this coverage level but adds no marginal meaning.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Return the remediation plan') and adds real scope detail: fewest changes that remove the most risk, choke points first, with two named output metrics (share of critical-path risk eliminated, routes cut). It distinguishes itself from list_attack_paths/enumerate tools by output shape, though it never names simulate_fix, the sibling it most needs to be separated from.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly frames when to reach for it: 'usually the right answer to what should we do'. That is genuine usage context. What is missing is any exclusion or alternative routing — notably no mention of when to prefer simulate_fix to test a candidate change instead of reading the plan.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

routes_to_targetA
Read-onlyIdempotent

Enumerate the k best distinct routes that reach a named sensitive asset. Answers 'how many ways in are there, and do they share a choke point' - which single-path views hide. Cutting a hop that every route traverses removes them all; cutting one that appears in a single route removes one. Each route comes with its hops, ready to hand to simulate_fix. It returns the best k and has no further page.

NameRequiredDescriptionDefault
kNoHow many distinct routes to return, best first. Fewer routes come back when fewer exist.
fromNoOptional entry point to start from, by name or node id. Omit to consider every internet-exposed entry point.
targetYesThe sensitive asset to reach, by name or node id: a `name` under get_posture's crownJewels (e.g. 'account-admin (effective)'), or an `id` from search_assets. A target nothing reaches returns an empty list, not an error.

Output Schema

NameRequiredDescription
kShortestPathsNoThe distinct routes to the target, most likely first. Empty when nothing reaches it.

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, non-destructive, so the safety profile is covered. The description adds genuine behavioral value beyond that: the 'no further page' note (no pagination), the hop-level payload that feeds simulate_fix, and the choke-point semantics of cutting shared vs unique hops.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded in the first sentence, and the remaining sentences each carry real content (choke-point reasoning, handoff to simulate_fix, no pagination). Slightly verbose but no filler that could be cut without losing behavior.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, the description need not detail return values, and it covers the remaining key behaviors: pagination absence, empty-list-not-error (in the target param), and the downstream simulate_fix handoff. An agent has what it needs to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so all three parameters are fully documented in the schema, establishing the baseline of 3. The description only echoes the 'k best' concept and does not add syntax or format meaning beyond what the schema already provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Enumerate the k best distinct routes that reach a named sensitive asset.' It contrasts itself against 'single-path views,' giving functional differentiation from the attack-path siblings, though it does not name them explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives clear context for use ('how many ways in are there, and do they share a choke point'), which implies selection over single-path tools, and routes the user forward to simulate_fix. It stops short of an explicit when-not-to-use or a named alternative.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_assetsA
Read-onlyIdempotent

Full-text search across indexed assets and findings by name, CVE id, or keyword. Use it to resolve a name a human mentioned into the node ids the other tools take. It needs OpenSearch on the deployment: where that is off it answers with an error saying so, never with an empty list, and get_posture and list_attack_paths are then where names come from. It returns the best size matches and has no further page: narrow the query instead.

NameRequiredDescriptionDefault
sizeNoMaximum number of matches to return, best match first.
queryYesText matched against asset and finding names, labels, ids, severities and CWEs, e.g. 'log4j', 'PII', 'CVE-2021-44228'.

Output Schema

NameRequiredDescription
searchYesThe matches, best first. Empty when nothing matches.

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover safety (readOnly, idempotent, non-destructive), yet the description adds substantial non-obvious behavior: a hard dependency on OpenSearch, that the disabled case returns an error rather than an empty list, and that results are non-paginated so the query must be narrowed instead.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four sentences ordered purpose → routing use case → deployment failure mode → result limits, with zero filler. The failure-mode caveat is placed exactly where a caller would look for it, and nothing is restated from the schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return shape need not be described; the description still covers the error-vs-empty distinction, the deployment prerequisite, and non-pagination behavior. For a two-parameter read tool this leaves no meaningful gap for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents both parameters, making 3 the baseline. The description earns above baseline by clarifying `size` semantics: results are best-match-first and there is no further page, so callers must narrow the query rather than raise size to page through.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource (full-text search across indexed assets and findings) and the match fields (name, CVE id, keyword). It explicitly separates itself from siblings by naming get_posture and list_attack_paths as the alternative source of names when OpenSearch is off.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit primary use case — resolve a human-mentioned name into the node ids other tools take — and an explicit when-not path: if OpenSearch is disabled, get_posture and list_attack_paths are where names come from. No routing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

simulate_fixA
Read-onlyIdempotent

Ask what actually happens if specific relationships are cut: re-runs the whole simulation with those edges removed and reports how many routes disappear and how the compromise probability moves. This is the tool to reach for before recommending a change - it is a deterministic counterfactual over the real graph, not an estimate, so it settles 'would this help' instead of arguing about it. It changes nothing: the graph, the environment and every recorded decision stay as they are, so it needs no confirmation and can be repeated freely.

NameRequiredDescriptionDefault
cutsYesRelationships to remove. Take each from a step of explain_attack_path (its from/to), or pass the `cut` of one of that route's remediations as it is.

Output Schema

NameRequiredDescription
whatIfNoThe same environment with the cuts applied, compared with it as it is.

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint and destructiveHint=false, so the safety profile is covered. The description nonetheless adds genuine value by characterizing the computation as a 'deterministic counterfactual... not an estimate' and noting it needs no confirmation and can be repeated freely, though parts of the 'changes nothing' claim overlap with the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The what-it-does and when-to-use content is front-loaded and the sentences are purposeful. The final sentence ('It changes nothing... repeated freely') is somewhat redundant with the annotations and slightly over-explained, keeping it below a 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be described, and the description supplies the decision context, safety posture and input provenance an agent needs. It is essentially complete, with only minor redundancy as its flaw.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the schema itself already documents cuts, from/to and edgeType, including the reference to explain_attack_path. The description adds the notion that relationships are the objects being cut but no format or interpretation detail beyond what the schema provides, so the baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource ('re-runs the whole simulation with those edges removed') and gives concrete outputs ('how many routes disappear and how the compromise probability moves'). It clearly differentiates itself from siblings like list_fixes and explain_attack_path by being a counterfactual simulation rather than a listing or explanation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit use context ('the tool to reach for before recommending a change') and clarifies the value proposition ('settles would this help instead of arguing about it'). It also points to explain_attack_path as the source of inputs, though it does not explicitly state when not to use it or name competing alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 8 tool updatesv1.38.1
    • Changedexplain_attack_path2 fields changed
      • changedInput schema / properties / path_id / description
        Previous value: -"The id from list_attack_paths."New value: +"The `id` of a route from list_attack_paths. An id lasts as long as its route: one that has since been closed is reported as no longer open, and list_attack_paths gives the current ones."
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "confidence": {
        +      "description": "How far the score can be trusted, 0-1: the mean confidence of its hops.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "confidenceLabel": {
        +      "description": "The same as a band: high, medium or low.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "correlatedHops": {
        +      "description": "True when two or more hops rest on the same kind of evidence.",
        +      "type": [
        +        "boolean",
        +        "null"
        +      ]
        +    },
        +    "id": {
        +      "description": "The route's id, as asked for.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "mixtureScore": {
        +      "description": "The score averaged over attacker profiles, weighted by how likely each is.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "nodes": {
        +      "description": "The assets on the route, entry point first, target last.",
        +      "items": {
        +        "properties": {
        +          "crownJewel": {
        +            "description": "True for a sensitive asset.",
        +            "type": [
        +              "boolean",
        +              "null"
        +            ]
        +          },
        +          "cvss": {
        +            "description": "For a vulnerability: its CVSS base score.",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "id": {
        +            "description": "Node id: what steps and cuts refer to.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "internetExposed": {
        +            "description": "True for an entry point reachable from the internet.",
        +            "type": [
        +              "boolean",
        +              "null"
        +            ]
        +          },
        +          "kev": {
        +            "description": "For a vulnerability: true when it is known to be exploited in the wild (CISA KEV).",
        +            "type": [
        +              "boolean",
        +              "null"
        +            ]
        +          },
        +          "label": {
        +            "description": "Asset type, e.g. LoadBalancer, Container, IAM_Role, Bucket, CVE.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "name": {
        +            "description": "Asset name.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "severity": {
        +            "description": "For a vulnerability: its severity.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          }
        +        },
        +        "type": [
        +          "object",
        +          "null"
        +        ]
        +      },
        +      "type": [
        +        "array",
        +        "null"
        +      ]
        +    },
        +    "posteriorMean": {
        +      "description": "The score with the uncertainty of each hop and the mix of attacker capabilities taken into account, 0-1.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "priority": {
        +      "description": "Triage priority, 0-100: the score blended with corroboration, target sensitivity and how many routes share the entry point. This is the order of the list.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "priorityFactors": {
        +      "description": "The reasons behind the priority, human-readable.",
        +      "items": {
        +        "type": [
        +          "string",
        +          "null"
        +        ]
        +      },
        +      "type": [
        +        "array",
        +        "null"
        +      ]
        +    },
        +    "priorityLabel": {
        +      "description": "Priority band: P1, P2 or P3. P1 is what to fix first.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "priorityReason": {
        +      "description": "One sentence saying why the route is in its band, when a fact put it there; null when the blended number did.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "profileScores": {
        +      "description": "The score against each attacker profile.",
        +      "items": {
        +        "properties": {
        +          "prior": {
        +            "description": "How likely this profile is assumed to be, 0-1.",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "profile": {
        +            "description": "Attacker profile: commodity, criminal or apt.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "score": {
        +            "description": "Probability this profile completes the route, 0-1.",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          }
        +        },
        +        "type": [
        +          "object",
        +          "null"
        +        ]
        +      },
        +      "type": [
        +        "array",
        +        "null"
        +      ]
        +    },
        +    "remediations": {
        +      "description": "Ways to close the route.",
        +      "items": {
        +        "properties": {
        +          "content": {
        +            "description": "The fix itself: the code or the policy to apply.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "cut": {
        +            "description": "The relationship this fix removes; null when it removes none. Pass it to simulate_fix as one of `cuts` to see what the fix would do.",
        +            "properties": {
        +              "from": {
        +                "description": "Node id the relationship starts at.",
        +                "type": [
        +                  "string",
        +                  "null"
        +                ]
        +              },
        +              "to": {
        +                "description": "Node id it ends at.",
        +                "type": [
        +                  "string",
        +                  "null"
        +                ]
        +              },
        +              "type": {
        +                "description": "The relationship type.",
        +                "type": [
        +                  "string",
        +                  "null"
        +                ]
        +              }
        +            },
        +            "type": [
        +              "object",
        +              "null"
        +            ]
        +          },
        +          "kind": {
        +            "description": "The form the fix takes, e.g. terraform.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "rationale": {
        +            "description": "Why this closes the route.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "title": {
        +            "description": "What to change, in a line.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          }
        +        },
        +        "type": [
        +          "object",
        +          "null"
        +        ]
        +      },
        +      "type": [
        +        "array",
        +        "null"
        +      ]
        +    },
        +    "runtimeConfirmed": {
        +      "description": "True when a runtime alert sits on the route.",
        +      "type": [
        +        "boolean",
        +        "null"
        +      ]
        +    },
        +    "score": {
        +      "description": "End-to-end probability that an attacker completes the route, 0-1. A modelled estimate, not a measured rate: see get_score_trust.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "scoreCiHigh": {
        +      "description": "High end of the same interval.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "scoreCiLow": {
        +      "description": "Low end of the 90% credible interval around posteriorMean. A wide interval means the score rests on soft inputs.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "scoreUpperBound": {
        +      "description": "The score if the hops fail or succeed together rather than independently: the weakest hop's probability.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "steps": {
        +      "description": "The hops, in order. Each is one relationship an attacker crosses.",
        +      "items": {
        +        "properties": {
        +          "attack": {
        +            "description": "The MITRE ATT&CK technique the hop maps to; null when none applies.",
        +            "properties": {
        +              "id": {
        +                "description": "Technique id, e.g. T1190.",
        +                "type": [
        +                  "string",
        +                  "null"
        +                ]
        +              },
        +              "name": {
        +                "description": "Technique name.",
        +                "type": [
        +                  "string",
        +                  "null"
        +                ]
        +              },
        +              "tactic": {
        +                "description": "The tactic it serves, e.g. Initial Access.",
        +                "type": [
        +                  "string",
        +                  "null"
        +                ]
        +              }
        +            },
        +            "type": [
        +              "object",
        +              "null"
        +            ]
        +          },
        +          "edgeType": {
        +            "description": "The relationship, e.g. EXPOSES, ASSUMES, HAS_PERMISSION, CAN_ESCALATE_TO.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "from": {
        +            "description": "Node id the hop starts at. Usable as `from` in a simulate_fix cut.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "probability": {
        +            "description": "Probability an attacker crosses this hop, 0-1.",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "to": {
        +            "description": "Node id the hop ends at. Usable as `to` in a simulate_fix cut.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "weightBasis": {
        +            "description": "Where that probability came from: kev, epss or runtime are observed evidence; cvss, severity or heuristic are estimates.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "weightConfidence": {
        +            "description": "How far to trust that probability, 0-1, given its basis.",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          }
        +        },
        +        "type": [
        +          "object",
        +          "null"
        +        ]
        +      },
        +      "type": [
        +        "array",
        +        "null"
        +      ]
        +    },
        +    "suppressed": {
        +      "description": "True when an analyst has triaged the route off the board (accepted risk, false positive). It still exists, but do not present it as open work.",
        +      "type": [
        +        "boolean",
        +        "null"
        +      ]
        +    }
        +  },
        +  "required": [
        +    "id"
        +  ],
        +  "type": "object"
        +}
    • Changedget_posture1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "applications": {
        +      "description": "The application names this environment knows: the values `app` arguments accept.",
        +      "items": {
        +        "type": [
        +          "string",
        +          "null"
        +        ]
        +      },
        +      "type": [
        +        "array",
        +        "null"
        +      ]
        +    },
        +    "posture": {
        +      "description": "Counts for the whole environment.",
        +      "properties": {
        +        "activePaths": {
        +          "description": "Routes still on the board, that is not suppressed.",
        +          "type": [
        +            "integer",
        +            "null"
        +          ]
        +        },
        +        "criticalPaths": {
        +          "description": "Routes found by the latest analysis, suppressed ones included: the number list_attack_paths pages through.",
        +          "type": [
        +            "integer",
        +            "null"
        +          ]
        +        },
        +        "edges": {
        +          "description": "Relationships in the graph.",
        +          "type": [
        +            "integer",
        +            "null"
        +          ]
        +        },
        +        "kevOnPaths": {
        +          "description": "Distinct known-exploited vulnerabilities (CISA KEV) sitting on a route.",
        +          "type": [
        +            "integer",
        +            "null"
        +          ]
        +        },
        +        "nodes": {
        +          "description": "Assets in the graph.",
        +          "type": [
        +            "integer",
        +            "null"
        +          ]
        +        },
        +        "policyViolations": {
        +          "description": "Violations of the policy invariants: rules the environment is never meant to break.",
        +          "type": [
        +            "integer",
        +            "null"
        +          ]
        +        },
        +        "runtimeConfirmed": {
        +          "description": "Open routes with a runtime alert on them: an attack may be in progress.",
        +          "type": [
        +            "integer",
        +            "null"
        +          ]
        +        },
        +        "suppressedPaths": {
        +          "description": "Routes an analyst has triaged off the board (accepted risk, false positive).",
        +          "type": [
        +            "integer",
        +            "null"
        +          ]
        +        }
        +      },
        +      "type": [
        +        "object",
        +        "null"
        +      ]
        +    },
        +    "riskSimulation": {
        +      "description": "The environment's overall risk, simulated over every route at once.",
        +      "properties": {
        +        "anyCompromiseProbability": {
        +          "description": "Probability that at least one sensitive asset is reached, 0-1. A modelled estimate, not a measured rate: see get_score_trust.",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "crownJewels": {
        +          "description": "The sensitive assets, each with the probability that an attacker reaches it.",
        +          "items": {
        +            "properties": {
        +              "compromiseProbability": {
        +                "description": "Probability this asset is reached, 0-1. A modelled estimate, not a measured rate: see get_score_trust.",
        +                "type": [
        +                  "number",
        +                  "null"
        +                ]
        +              },
        +              "label": {
        +                "description": "Asset type, e.g. IAM_Role, Bucket, Database.",
        +                "type": [
        +                  "string",
        +                  "null"
        +                ]
        +              },
        +              "name": {
        +                "description": "Asset name. Pass it to routes_to_target as `target`.",
        +                "type": [
        +                  "string",
        +                  "null"
        +                ]
        +              }
        +            },
        +            "type": [
        +              "object",
        +              "null"
        +            ]
        +          },
        +          "type": [
        +            "array",
        +            "null"
        +          ]
        +        },
        +        "sensitivityHigh": {
        +          "description": "High end of the same band.",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "sensitivityLow": {
        +          "description": "Low end of that probability when every hop is resampled within its uncertainty.",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        }
        +      },
        +      "type": [
        +        "object",
        +        "null"
        +      ]
        +    }
        +  },
        +  "type": "object"
        +}
    • Changedget_score_trust1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "calibration": {
        +      "description": "The scores compared with the recorded red-team and BAS outcomes.",
        +      "properties": {
        +        "brier": {
        +          "description": "Mean squared gap between score and outcome, 0-1. Lower is better.",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "diagnosis": {
        +          "description": "What to do about the gap, in one line.",
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "discrimination": {
        +          "description": "Whether the score puts confirmed routes above refuted ones.",
        +          "properties": {
        +            "auc": {
        +              "description": "Probability that a confirmed route outranks a refuted one. 0.5 is a coin. Null until there is one of each.",
        +              "type": [
        +                "number",
        +                "null"
        +              ]
        +            },
        +            "aucHigh": {
        +              "description": "High end. An interval containing 0.5 cannot be told from chance.",
        +              "type": [
        +                "number",
        +                "null"
        +              ]
        +            },
        +            "aucLow": {
        +              "description": "Low end of its approximate 95% interval.",
        +              "type": [
        +                "number",
        +                "null"
        +              ]
        +            },
        +            "hasData": {
        +              "description": "False when no outcome has been recorded.",
        +              "type": [
        +                "boolean",
        +                "null"
        +              ]
        +            },
        +            "negatives": {
        +              "description": "Refuted outcomes behind the figure.",
        +              "type": [
        +                "integer",
        +                "null"
        +              ]
        +            },
        +            "positives": {
        +              "description": "Confirmed outcomes behind the figure.",
        +              "type": [
        +                "integer",
        +                "null"
        +              ]
        +            },
        +            "verdict": {
        +              "description": "discriminates, indistinguishable-from-chance, inverted or insufficient-data.",
        +              "type": [
        +                "string",
        +                "null"
        +              ]
        +            }
        +          },
        +          "type": [
        +            "object",
        +            "null"
        +          ]
        +        },
        +        "ece": {
        +          "description": "Expected calibration error, 0-1. Lower is better.",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "hasData": {
        +          "description": "False when no outcome has been recorded: every score is then an expert estimate.",
        +          "type": [
        +            "boolean",
        +            "null"
        +          ]
        +        },
        +        "meanPredicted": {
        +          "description": "The mean score of the tested routes.",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "observedRate": {
        +          "description": "The share of them that were confirmed.",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "priorityDiscrimination": {
        +          "description": "Whether the triage order puts confirmed routes above refuted ones.",
        +          "properties": {
        +            "auc": {
        +              "description": "Probability that a confirmed route outranks a refuted one. 0.5 is a coin. Null until there is one of each.",
        +              "type": [
        +                "number",
        +                "null"
        +              ]
        +            },
        +            "aucHigh": {
        +              "description": "High end. An interval containing 0.5 cannot be told from chance.",
        +              "type": [
        +                "number",
        +                "null"
        +              ]
        +            },
        +            "aucLow": {
        +              "description": "Low end of its approximate 95% interval.",
        +              "type": [
        +                "number",
        +                "null"
        +              ]
        +            },
        +            "hasData": {
        +              "description": "False when no outcome has been recorded.",
        +              "type": [
        +                "boolean",
        +                "null"
        +              ]
        +            },
        +            "negatives": {
        +              "description": "Refuted outcomes behind the figure.",
        +              "type": [
        +                "integer",
        +                "null"
        +              ]
        +            },
        +            "positives": {
        +              "description": "Confirmed outcomes behind the figure.",
        +              "type": [
        +                "integer",
        +                "null"
        +              ]
        +            },
        +            "verdict": {
        +              "description": "discriminates, indistinguishable-from-chance, inverted or insufficient-data.",
        +              "type": [
        +                "string",
        +                "null"
        +              ]
        +            }
        +          },
        +          "type": [
        +            "object",
        +            "null"
        +          ]
        +        },
        +        "recommendedScale": {
        +          "description": "Observed rate over predicted: below 1 the scores run high, above 1 low. Null until the sample is large enough.",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "samples": {
        +          "description": "Tested routes that carried a score: the size of the sample.",
        +          "type": [
        +            "integer",
        +            "null"
        +          ]
        +        },
        +        "verdict": {
        +          "description": "well-calibrated, calibrated-on-average, overconfident, underconfident or insufficient-data.",
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        }
        +      },
        +      "type": [
        +        "object",
        +        "null"
        +      ]
        +    },
        +    "validation": {
        +      "description": "What happened to the routes that were tested.",
        +      "properties": {
        +        "confirmed": {
        +          "description": "Tested routes an attacker could really follow.",
        +          "type": [
        +            "integer",
        +            "null"
        +          ]
        +        },
        +        "missed": {
        +          "description": "Real routes a tester found that the engine had not reported: false negatives.",
        +          "type": [
        +            "integer",
        +            "null"
        +          ]
        +        },
        +        "partial": {
        +          "description": "Tested routes that worked in part.",
        +          "type": [
        +            "integer",
        +            "null"
        +          ]
        +        },
        +        "precision": {
        +          "description": "confirmed / (confirmed + refuted). Null until one of the two exists.",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "recall": {
        +          "description": "confirmed / (confirmed + missed). Null until one of the two exists.",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "refuted": {
        +          "description": "Tested routes that turned out not to work: false positives.",
        +          "type": [
        +            "integer",
        +            "null"
        +          ]
        +        },
        +        "tested": {
        +          "description": "Routes the engine reported that were then tested.",
        +          "type": [
        +            "integer",
        +            "null"
        +          ]
        +        }
        +      },
        +      "type": [
        +        "object",
        +        "null"
        +      ]
        +    }
        +  },
        +  "type": "object"
        +}
    • Changedlist_attack_paths3 fields changed
      • changedInput schema / properties / app / description
        Previous value: -"Optional application scope; omit for the whole environment."New value: +"Restrict to one application, by a name from get_posture's `applications`: a repository slug or the value of a cloud resource's `app` tag, e.g. 'payments-api'. The match is exact, and a name the environment does not know returns nothing. Omit for the whole environment."
      • addedInput schema / properties / offset
        Added value: +{
        +  "default": 0,
        +  "description": "How many routes to skip from the top of the ranking: the `nextOffset` of the previous page. Leave it out for the first page.",
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "attackPaths": {
        +      "description": "The routes, highest priority first.",
        +      "items": {
        +        "properties": {
        +          "confidenceLabel": {
        +            "description": "How far the score can be trusted given where its hop probabilities came from: high, medium or low.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "correlatedHops": {
        +            "description": "True when two or more hops rest on the same kind of evidence, a concrete reason they may not be independent.",
        +            "type": [
        +              "boolean",
        +              "null"
        +            ]
        +          },
        +          "id": {
        +            "description": "Stable id of the route. Pass it to explain_attack_path.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "nodes": {
        +            "description": "The assets the route passes through, entry point first, target last.",
        +            "items": {
        +              "properties": {
        +                "crownJewel": {
        +                  "description": "True for a sensitive asset.",
        +                  "type": [
        +                    "boolean",
        +                    "null"
        +                  ]
        +                },
        +                "internetExposed": {
        +                  "description": "True for an entry point reachable from the internet.",
        +                  "type": [
        +                    "boolean",
        +                    "null"
        +                  ]
        +                },
        +                "label": {
        +                  "description": "Asset type, e.g. LoadBalancer, Container, IAM_Role, Bucket, CVE.",
        +                  "type": [
        +                    "string",
        +                    "null"
        +                  ]
        +                },
        +                "name": {
        +                  "description": "Asset name.",
        +                  "type": [
        +                    "string",
        +                    "null"
        +                  ]
        +                }
        +              },
        +              "type": [
        +                "object",
        +                "null"
        +              ]
        +            },
        +            "type": [
        +              "array",
        +              "null"
        +            ]
        +          },
        +          "priority": {
        +            "description": "Triage priority, 0-100: the score blended with corroboration, target sensitivity and how many routes share the entry point. This is the order of the list.",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "priorityFactors": {
        +            "description": "The reasons behind the priority, human-readable.",
        +            "items": {
        +              "type": [
        +                "string",
        +                "null"
        +              ]
        +            },
        +            "type": [
        +              "array",
        +              "null"
        +            ]
        +          },
        +          "priorityLabel": {
        +            "description": "Priority band: P1, P2 or P3. P1 is what to fix first.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "priorityReason": {
        +            "description": "One sentence saying why the route is in its band, when a fact put it there (a runtime alert, a known-exploited weakness); null when the blended number did.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "runtimeConfirmed": {
        +            "description": "True when a runtime alert sits on the route.",
        +            "type": [
        +              "boolean",
        +              "null"
        +            ]
        +          },
        +          "score": {
        +            "description": "End-to-end probability that an attacker completes the route, 0-1. A modelled estimate, not a measured rate: see get_score_trust.",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "scoreUpperBound": {
        +            "description": "The score if the hops fail or succeed together rather than independently: the weakest hop's probability. The truth lies between score and this.",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "suppressed": {
        +            "description": "True when an analyst has triaged the route off the board (accepted risk, false positive). It still exists, but do not present it as open work.",
        +            "type": [
        +              "boolean",
        +              "null"
        +            ]
        +          }
        +        },
        +        "type": [
        +          "object",
        +          "null"
        +        ]
        +      },
        +      "type": [
        +        "array",
        +        "null"
        +      ]
        +    },
        +    "nextOffset": {
        +      "description": "Pass as `offset` for the next page. Null when this page is the last.",
        +      "type": [
        +        "integer",
        +        "null"
        +      ]
        +    },
        +    "offset": {
        +      "description": "How many routes were skipped before this page.",
        +      "type": "integer"
        +    }
        +  },
        +  "required": [
        +    "attackPaths",
        +    "offset",
        +    "nextOffset"
        +  ],
        +  "type": "object"
        +}
    • Changedlist_fixes2 fields changed
      • changedInput schema / properties / app / description
        Previous value: -"Optional application scope; omit for the whole environment."New value: +"Restrict to one application, by a name from get_posture's `applications`: a repository slug or the value of a cloud resource's `app` tag, e.g. 'payments-api'. The match is exact, and a name the environment does not know returns nothing. Omit for the whole environment."
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "remediationPlan": {
        +      "description": "The changes, the one that removes the most risk first.",
        +      "items": {
        +        "properties": {
        +          "coveragePct": {
        +            "description": "That sum as a share of the risk on all routes, 0-1.",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "filename": {
        +            "description": "A suggested file name for the fix.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "kind": {
        +            "description": "The form the fix takes, e.g. terraform.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "pathCount": {
        +            "description": "How many routes this change cuts.",
        +            "type": [
        +              "integer",
        +              "null"
        +            ]
        +          },
        +          "rationale": {
        +            "description": "Why this change closes the routes it closes.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "riskCovered": {
        +            "description": "The summed score of the routes it cuts.",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "title": {
        +            "description": "What to change, in a line.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          }
        +        },
        +        "type": [
        +          "object",
        +          "null"
        +        ]
        +      },
        +      "type": [
        +        "array",
        +        "null"
        +      ]
        +    }
        +  },
        +  "type": "object"
        +}
    • Changedroutes_to_target3 fields changed
      • changedInput schema / properties / from / description
        Previous value: -"Optional entry-point name to start from."New value: +"Optional entry point to start from, by name or node id. Omit to consider every internet-exposed entry point."
      • changedInput schema / properties / target / description
        Previous value: -"Sensitive asset name, e.g. 'account-admin (effective)'."New value: +"The sensitive asset to reach, by name or node id: a `name` under get_posture's crownJewels (e.g. 'account-admin (effective)'), or an `id` from search_assets. A target nothing reaches returns an empty list, not an error."
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "kShortestPaths": {
        +      "description": "The distinct routes to the target, most likely first. Empty when nothing reaches it.",
        +      "items": {
        +        "properties": {
        +          "id": {
        +            "description": "Id of the route. explain_attack_path takes it only when list_attack_paths returns the same route; an alternate that exists only here is described by its steps below.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "nodes": {
        +            "description": "The assets the route passes through, entry point first, target last. A node that appears in every route is a choke point.",
        +            "items": {
        +              "properties": {
        +                "label": {
        +                  "description": "Asset type, e.g. LoadBalancer, Container, IAM_Role, Bucket, CVE.",
        +                  "type": [
        +                    "string",
        +                    "null"
        +                  ]
        +                },
        +                "name": {
        +                  "description": "Asset name.",
        +                  "type": [
        +                    "string",
        +                    "null"
        +                  ]
        +                }
        +              },
        +              "type": [
        +                "object",
        +                "null"
        +              ]
        +            },
        +            "type": [
        +              "array",
        +              "null"
        +            ]
        +          },
        +          "score": {
        +            "description": "End-to-end probability that an attacker completes the route, 0-1. A modelled estimate, not a measured rate: see get_score_trust.",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "steps": {
        +            "description": "The hops, in order.",
        +            "items": {
        +              "properties": {
        +                "edgeType": {
        +                  "description": "The relationship, e.g. EXPOSES, ASSUMES, HAS_PERMISSION, CAN_ESCALATE_TO.",
        +                  "type": [
        +                    "string",
        +                    "null"
        +                  ]
        +                },
        +                "from": {
        +                  "description": "Node id the hop starts at. Usable as `from` in a simulate_fix cut.",
        +                  "type": [
        +                    "string",
        +                    "null"
        +                  ]
        +                },
        +                "probability": {
        +                  "description": "Probability an attacker crosses this hop, 0-1.",
        +                  "type": [
        +                    "number",
        +                    "null"
        +                  ]
        +                },
        +                "to": {
        +                  "description": "Node id the hop ends at. Usable as `to` in a simulate_fix cut.",
        +                  "type": [
        +                    "string",
        +                    "null"
        +                  ]
        +                },
        +                "weightBasis": {
        +                  "description": "Where that probability came from: kev, epss or runtime are observed evidence; cvss, severity or heuristic are estimates.",
        +                  "type": [
        +                    "string",
        +                    "null"
        +                  ]
        +                }
        +              },
        +              "type": [
        +                "object",
        +                "null"
        +              ]
        +            },
        +            "type": [
        +              "array",
        +              "null"
        +            ]
        +          }
        +        },
        +        "type": [
        +          "object",
        +          "null"
        +        ]
        +      },
        +      "type": [
        +        "array",
        +        "null"
        +      ]
        +    }
        +  },
        +  "type": "object"
        +}
    • Changedsearch_assets1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "search": {
        +      "description": "The matches, best first. Empty when nothing matches.",
        +      "items": {
        +        "properties": {
        +          "id": {
        +            "description": "Node id: usable as `target` or `from` in routes_to_target and in simulate_fix cuts.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "label": {
        +            "description": "Its type, e.g. Container, IAM_Role, CVE.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "name": {
        +            "description": "Asset or finding name.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "score": {
        +            "description": "Relevance of the match. Comparable only within one result.",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          }
        +        },
        +        "type": [
        +          "object",
        +          "null"
        +        ]
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "search"
        +  ],
        +  "type": "object"
        +}
    • Changedsimulate_fix3 fields changed
      • changedInput schema / properties / cuts / description
        Previous value: -"Relationships to remove. Use node ids from explain_attack_path steps (from/to)."New value: +"Relationships to remove. Take each from a step of explain_attack_path (its from/to), or pass the `cut` of one of that route's remediations as it is."
      • addedInput schema / properties / cuts / items / properties / type
        Added value: +{
        +  "description": "Optional relationship type to cut: the step's `edgeType`, e.g. EXPOSES or HAS_PERMISSION. Omit to cut every relationship from `from` to `to`.",
        +  "type": "string"
        +}
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "whatIf": {
        +      "description": "The same environment with the cuts applied, compared with it as it is.",
        +      "properties": {
        +        "after": {
        +          "description": "The routes still open with the cuts applied.",
        +          "items": {
        +            "properties": {
        +              "id": {
        +                "description": "Route id.",
        +                "type": [
        +                  "string",
        +                  "null"
        +                ]
        +              }
        +            },
        +            "type": [
        +              "object",
        +              "null"
        +            ]
        +          },
        +          "type": [
        +            "array",
        +            "null"
        +          ]
        +        },
        +        "afterRisk": {
        +          "properties": {
        +            "anyCompromiseProbability": {
        +              "description": "Probability that at least one sensitive asset is reached with the cuts applied, 0-1. A modelled estimate, not a measured rate: see get_score_trust.",
        +              "type": [
        +                "number",
        +                "null"
        +              ]
        +            }
        +          },
        +          "type": [
        +            "object",
        +            "null"
        +          ]
        +        },
        +        "beforeRisk": {
        +          "properties": {
        +            "anyCompromiseProbability": {
        +              "description": "Probability that at least one sensitive asset is reached as things are, 0-1. A modelled estimate, not a measured rate: see get_score_trust.",
        +              "type": [
        +                "number",
        +                "null"
        +              ]
        +            }
        +          },
        +          "type": [
        +            "object",
        +            "null"
        +          ]
        +        },
        +        "removedEdges": {
        +          "description": "How many relationships the cuts matched. 0 means they matched nothing: check the node ids and the type.",
        +          "type": [
        +            "integer",
        +            "null"
        +          ]
        +        },
        +        "riskReduction": {
        +          "description": "How much the probability that any sensitive asset is reached drops, 0-1. It stays 0 while one asset is still reached in every trial.",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        }
        +      },
        +      "type": [
        +        "object",
        +        "null"
        +      ]
        +    }
        +  },
        +  "type": "object"
        +}

TDQS

A4.2/5.0

Scored across 8 tools

Disambiguation5/5

Each tool has a distinct role: get_posture orients, list_attack_paths enumerates globally, explain_attack_path details one route, routes_to_target enumerates to a named asset, list_fixes gives remediation, search_assets resolves names, get_score_trust reports calibration, and simulate_fix runs counterfactuals. The descriptions explicitly disambiguate the potentially overlapping route tools (global listing vs target-specific k-best vs single-route detail) and when to reach for each.

Naming Consistency4/5

Seven of eight names follow a clean snake_case verb_noun pattern (get_posture, list_attack_paths, explain_attack_path, list_fixes, search_assets, get_score_trust, simulate_fix). Only routes_to_target breaks the convention with a noun_to_noun form, a minor deviation but still readable.

Tool Count5/5

Eight tools is well-scoped for an attack-path exposure analysis domain, with each tool covering a genuine capability (orientation, enumeration, detail, target analysis, remediation, search, calibration, simulation). No tool feels redundant or padded.

Completeness4/5

Coverage of the analysis lifecycle is strong: orient, enumerate, explain, remediate, verify, and self-assess score trust. Minor gaps exist—no tool manages the analyst suppression referenced by list_attack_paths, and there is no export/report operation—but core workflows are complete for a read-only analytical surface.

Maintenance

ActivityActive
ResponsivenessSlow

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    A local-first AWS security tool that uses graph theory to discover attack paths (e.g., Internet → Role → DB) and prioritize remediations. It allows agents to perform read-only security audits and generate Terraform fixes without data exfiltration.
    15
    28 PyPI
    4
    Apache 2.0
  • A
    license
    A
    quality
    A
    maintenance
    Enables MCP-aware agents to read and explore Oracle Cloud Infrastructure — listing, fetching, and searching across 39 resource types spanning Identity, Compute, Block Volume, Networking, Object Storage, and OKE. Runs locally over stdio using credentials from ~/.oci/config, with fail-closed safety gates and audit logging governing future write operations.
    4
    Apache 2.0