Skip to main content
Glama
luiacuaniello

PerspectiveGraph

PerspectiveGraph

CI Release Go License OpenSSF Best Practices Artifact Hub

Catch the attack path in the pull request that opens it - then ship the fix as a PR.

On every pull request, PerspectiveGraph (open source, Apache 2.0) answers one question against a graph of your real environment - built from the scanners you already run (Trivy, Semgrep, Cloud Custodian, Falco):

Does this change open a path from the internet, through excessive privilege, to something valuable?

When it does, the PR check goes red - a required status you can block the merge on - and you get the fix as its own one-click pull request. The reachable attack path is caught and closed in code review, where it's cheapest, not months later in production. This is shift-left attack-path analysis: not a scanner bolted onto CI, not a runtime CNAPP you log into after the fact - the reachability question, answered in the developer's workflow.

That gate is powered by a full attack-path correlation engine, so the same graph also gives you the rest: a queryable dashboard of your ~5 critical attack paths (not 10,000 flat findings), triage, runtime confirmation, an AI summary, and always-current architecture maps. But the wedge is the pull request.

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

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

What this has not done yet

The engine reports probabilities, credible intervals and its own calibration - Brier score, ECE, a reliability diagram. None of that has been calibrated against field data. Nobody has yet run it over a real estate, tested the paths it surfaced, and fed the verdicts back. The machinery for that closed loop is built and tested; the loop has not been closed with real outcomes.

So read a score as "what this model believes, and how sure it says it is", not as a measured frequency. A path at 0.7 has not been shown to be exploited seven times in ten - it has been shown to be what the model concludes from the evidence it was given, and the interval beside it says how thin that evidence is.

That is a statement about maturity, not about intent: the calibration harness exists precisely so that number can be earned rather than asserted, and the CloudGoat benchmark grades the path-finding itself on public, reproducible scenarios today. If you run this on a real environment and record what you find, that is the contribution that matters most.

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: every check is one iam:SimulatePrincipalPolicy call, a dry run that evaluates policy without performing anything, so it creates nothing and costs nothing. It needs iam:SimulatePrincipalPolicy and iam:ListRoles - both inside SecurityAudit. Binaries for linux/macOS (amd64, arm64) and Windows are on the releases page, signed with cosign and carrying SLSA build provenance. The signature covers SHA256SUMS, so one check covers every archive:

cosign verify-blob --bundle SHA256SUMS.bundle \
  --certificate-identity-regexp 'https://github.com/luiacuaniello/perspectivegraph/.*' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  SHA256SUMS && sha256sum -c SHA256SUMS --ignore-missing

Add -compare and it also runs the engine over the same account and exits non-zero where the two disagree - each disagreement is a false positive or a miss, in the engine or in your assumptions. That check is how the permission-boundary bug described in the manual was found, and how it stays closed.

That command is also stage 0 of a fuller trial: how to evaluate this walks from here to a verdict in stages that each end in an answer - and says what the trial will not tell you before you spend a fortnight finding out.

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, waits for the analyzer, and prints the top attack path with its generated fix. Dashboard on http://localhost:3000. Needs Docker, jq and curl - no Go or Node toolchain, and nothing is compiled: measured at 23 seconds from an empty image cache. Tear down with make down.

Building it yourself instead is make demo-build, which is the same demo from your working tree. The images the fast path runs are the release artefacts, so you can check what you are about to run before you run it - the cosign verify command is at the top of docker-compose.demo.yml.

Prefer not to build? The release images are published to GHCR (latest also tracks the newest release; the pinned tag is the one to use if you care about reproducibility):

docker pull ghcr.io/luiacuaniello/perspectivegraph:v1.14.1 # x-release-please-version
docker pull ghcr.io/luiacuaniello/perspectivegraph-dashboard:v1.14.1 # x-release-please-version
docker pull ghcr.io/luiacuaniello/perspectivegraph-postgres:v1.14.1 # x-release-please-version

On Kubernetes, the Helm chart is published the same way - no clone needed, and a version you can pin and verify. It is listed on Artifact Hub as an official package from a verified publisher, which is Artifact Hub's way of saying the chart is published by the people who wrote the software rather than by a third party repackaging it:

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

They are signed with cosign keyless and carry an SPDX SBOM plus a SLSA build provenance attestation - verify before you run, rather than taking the supply chain on trust:

cosign verify \
  --certificate-identity-regexp 'https://github.com/luiacuaniello/perspectivegraph/.*' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/luiacuaniello/perspectivegraph:v1.14.1 # x-release-please-version

The dashboard opens on the decision, not the inventory: what is being exploited right now, the fewest changes that remove the most risk, and how much the numbers can be trusted.

The day's decision surface

Routes are ranked by a composite triage priority - what the route reaches, whether runtime confirmed it, how exposed the entry is - not by raw exploit score, so a lower-scoring route can and does outrank a higher-scoring one.

Attack path detail

Score calibration

Every hop, its probability, where that probability came from, and the ATT&CK technique.

Whether the engine's own scores held up against recorded outcomes.

Every screenshot on this page is make demo: sample scanner output and seeded verdicts, not a real environment. That is why the calibration panel returns a verdict of "underconfident" - across 14 seeded outcomes the engine predicted 60% where 71% held up. Those outcomes were generated to exercise the instrument, not to flatter it. On a fresh install the same page reads "insufficient data" and withholds a verdict until real outcomes exist, because a risk score you cannot check is worth less than an honest blank.

Why?

Modern security teams don't suffer from a lack of tools - they suffer from noise, fragmentation, and missing context.

Role

Pain today

What PerspectiveGraph gives them

Developer

CI/CD blocked by thousands of irrelevant CVEs

A PR check that goes red only when the change opens a real internet→sensitive-asset path - plus the fix as a one-click PR

Security

Triage on flat lists of 10,000 findings

A ranked list of ~5 critical attack paths, queryable like a database

Architect

No live view of how IaC becomes attack surface

Auto-generated, always-current architecture & data-flow maps + drift detection

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

An estate is not optional, and that is the point: without one there are no attack paths, only a flat list of findings - the thing this replaces. If you collect your estate on its own schedule, pass estate: estate.json (what perspectivegraph awscollect -json writes) instead of aws-region.

Already running the engine? Point at it and it keeps the graph across pull requests, plus triage, history and the dashboard:

- uses: luiacuaniello/perspectivegraph@v1
  with:
    api: https://perspectivegraph.internal
    ingest: https://perspectivegraph.internal:8081
    report: trivy.json
    hmac-secret: ${{ secrets.PG_INGEST_HMAC }}

Both modes run the same normalizer, the same pathfinder and the same triage priority, and return the same verdict - a test asserts they agree path-for-path on identical input.

The check goes red when this commit puts a sensitive asset within reach. 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. That is a different question from the one your other scanners answer, and answering it needs a live estate, which is why the action talks to a running engine instead of scanning the runner.

It has three outcomes, and the third is the point. Every two-state gate ever written gives a pipeline whose scanner output never arrived the same green tick as one that is genuinely clean. Here that is unknown, and it fails the build by default:

Verdict

Exit

Meaning

clean

0

The engine analysed this commit and found no path through it

blocked

1

Critical attack paths run through it - the check names them

unknown

2

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

Set allow-unknown: true while you roll the gate out. Leaving it on afterwards turns a broken ingest back into a green check, which is the one thing this gate is for.

The same thing without GitHub Actions - the action is a thin wrapper over one command:

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

Two things to settle before wiring it up.

Fork pull requests. The gate needs secrets, and GitHub gives a fork's pull_request run none - so a fork PR cannot be analysed and fails closed as unknown. Do not reach for pull_request_target to work around it: that event runs with your secrets against the contributor's code, and in local mode your secrets are cloud credentials. Run the gate on push to your own branches instead, and let fork PRs go without it.

Public repositories. When it blocks, the check prints the route - real asset names, the CVE linking them, the sensitive asset at the end - into the job log and summary, which on a public repository are public. Use soft-fail and post the detail somewhere private, or keep the gate on a private repository.

Full input reference in action.yml; the underlying query is prVerdict in the API schema.

Let an agent query it

A language model is weak at exactly what this engine is good at: it cannot enumerate fourteen thousand edges reliably, it does not run Dijkstra, and asked for "the attack paths in my account" it will produce plausible routes that do not exist. So the engine speaks MCP - an agent calls it and reasons over answers it could not have invented.

make mcp    # or: perspectivegraph mcp --api http://localhost:8080
{"mcpServers": {"perspectivegraph": {
  "command": "perspectivegraph",
  "args": ["mcp", "--api", "http://localhost:8080"]}}}

Eight tools: get_posture, list_attack_paths, explain_attack_path, routes_to_target, list_fixes, simulate_fix, search_assets, get_score_trust. The one worth the integration is simulate_fix - it re-runs the whole simulation with the given edges cut and reports what actually changes, settling "would this help" with a deterministic counterfactual instead of an argument.

The surface is read-only: nothing suppresses a path, opens a PR or records a verdict, because an agent that can silently accept a risk is a liability rather than a feature. Every tool declares that on the wire (readOnlyHint), so a host can decide what to run unattended without taking this paragraph's word for it - and a test fails if a tool is ever added without that decision. The descriptions also tell the model the scores are expert estimates, and to call get_score_trust before quoting one as a probability. Every tool is also run against the real engine in the test suite, not only a stub, so a query naming a field the schema lacks fails the build instead of an agent's call. search_assets asks the engine whether full-text search is on: without OpenSearch an agent is told so, instead of receiving an empty result that reads as "no asset by that name".

The server is in the official MCP Registry as io.github.luiacuaniello/perspectivegraph, published from every stable release, and on Glama, which builds it, inspects the tools it exposes and grades their definitions - a grade of the MCP surface, not of the engine's scores:

PerspectiveGraph MCP server on Glama

Project status & maturity

The short version, if you read nothing else. The engine and its public API are complete, documented and tested. The AWS connector is verified against a real account. The path scores are not calibrated against real exploited outcomes yet, and the order they produce has not been graded against them either - the instruments for both exist, the field verdicts do not. What is measured today is whether a surfaced path is real (the benchmark below). So: use it to find and cut routes, and don't put its risk percentage in front of a board. What is and isn't claimed is spelled out in positioning. It collects no telemetry: out of the box it opens no outbound connection at all - GitHub, the AI assistant and the KEV/EPSS feeds each stay dark until you set a key or flag (THREATINTEL is off by default).

The benchmark, as of v1.14.1. make bench-cloudgoat runs four CloudGoat-shaped scenarios in CI and grades the engine on each:

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. Read that for what it is: four scenarios, two of them negative controls - a regression gate against known shapes, not a measurement of field accuracy on your estate.

The long version follows. PerspectiveGraph is 1.x and in active development, built in the open. The GraphQL schema is frozen and drift-guarded and the CLI/config surface is documented, so a breaking change goes through a major version rather than arriving in a patch - see the API stability policy. What's next is in the roadmap; read this before you rely on it:

  • Engine: feature-complete. The correlation engine, agentless connectors, triage, SSO, the PR merge-gate, the AI assistant, and the scale work are all implemented and covered by tests. The public API contract (GraphQL, ingest events, config, CLI) is documented and the GraphQL schema is frozen + drift-guarded - see the API stability policy.

  • Connector: validated against a real AWS account; scores: not yet field-calibrated. The live connector, its read-only grant (SecurityAudit covers every call), cross-account AssumeRole, and the network↔identity join (instance --ASSUMES--> role) are verified against a real account - that last edge was in fact a gap only real-account testing exposed (the fixtures already contained edges AWS makes you derive). The reachability-precision claim is verified there too: make reachability-lab-aws stands up two instances behind the same wide-open security group, one in a subnet routed to an internet gateway and one in a subnet with no default route, and the engine marks only the first as exposed - suppressing the second with the reason, on real AWS rather than fixtures. What is not yet done is calibrating the path scores against real exploited outcomes: the self-calibration flywheel has run end-to-end only on deliberately-vulnerable synthetic targets (a log4shell app, a kind RBAC scenario). Treat the scores as directionally honest, not production-calibrated. One half of that gap is now closed against real AWS for free: make redteam-aws grades the engine's privilege-escalation claims with AWS's own policy evaluator - a read-only dry run that creates nothing and applies the SCPs and condition keys the engine's policy reader skips. That grading has already paid for itself: make boundary-lab-aws stands up two roles with an identical privesc policy that differ only in a permissions boundary, and it caught the engine calling both escalating where AWS allowed one and denied the other. That false positive is now fixed - the connector carries the boundary through and the evaluator intersects it - and the lab is the regression test, running the engine and AWS side by side on a real account and failing if they disagree. It deliberately does not rescale the path scores, and the manual explains why it cannot: those verdicts are one-sided, and a censored sample is not a measurement. The make validate-aws, make validate-harness-aws, and make validate-harness* harnesses are the path to closing that on your own environment. For an offline, CI-gated check that the engine actually finds the right paths, make bench-cloudgoat grades it against a battery of CloudGoat-shaped ground-truth scenarios (precision/recall) - including the reachability-precision case (an open SG on a private-subnet box must not form a path) and the credential-origin case (a leaked-key privesc is invisible until SEED_IAM_USERS is on). It runs under make test, so a regression that loses or invents a path fails the build.

  • Deployment: demo-grade defaults, with a production switch. The backend is hardened wherever it runs (distroless, non-root, read-only rootfs, all capabilities dropped, digest-pinned 0-CVE images, opt-in TLS). Under docker compose the bundled dependencies - the dashboard's nginx, NATS, the demo Postgres - run as their images ship, because the demo has to stay one command. Under Helm every workload, init containers included, satisfies the restricted Pod Security Standard, asserted in CI on both value sets, so a namespace that enforces it admits the chart unmodified. The demo defaults are otherwise deliberately open. Set PG_ENV=production and the backend refuses to start unless both the API and ingest are authenticated - the permissive default cannot be reached by forgetting to configure it. A production rollout still needs your own hardening beyond that: an external PostgreSQL+AGE - managed only on Azure, self-managed on AWS and GCP, because neither offers the AGE extension (the matrix) - secrets in a manager (not env vars), TLS on by default, backups, and HA for the leader-gated scheduler. If you terminate at a reverse proxy or ingress, set TRUSTED_PROXY_CIDRS to it: per-IP controls (rate limit, brute-force lockout, the address in the audit trail) otherwise key on the connecting peer - correct and unspoofable, but behind a proxy that is one key for everybody, so one attacker's failed logins lock out every user. X-Forwarded-For is believed only from the proxies named there, and only the hops they added. For people use OIDC, so revoking access is your IdP's job rather than a token rotation - see the operations & hardening runbook, SECURITY.md, and the threat model.

  • Support: the newest release, and nothing behind it. There are no backports and no LTS branch - at six minor releases in the eight days after 1.0, a maintenance branch would be a promise one maintainer breaks. What is promised instead is a clock on security fixes (Critical 7 days, High 30, from confirmation) and an upgrade specified rather than hoped for: semver over an enumerated stable surface, a drift-guarded schema, no migration step, and rollback by redeploying the previous digest. SUPPORT.md is the policy, including how to run this where change control applies.

  • Scope. It answers the reachable attack-path question in the developer workflow. It is not a scanner, a CNAPP, or a compliance product, and it does not replace them.

  • How it is written. Developed by a human working with Claude (Anthropic): the design decisions and what ships are the maintainer's, a large share of the implementation and its tests came out of that collaboration. Said plainly for the same reason the engine reports its own calibration - a claim you can check beats one you have to accept. Check it: make test, make bench-cloudgoat, govulncheck ./..., gosec ./.... See CONTRIBUTING.

Issues and PRs are welcome. Nothing here is claimed beyond what the tests and the listed validation cover.

Documentation

The manual is the full reference: the scoring model, every integration, deployment, hardening and the runbook for pointing it at your own environment.

Verify the claims rather than taking them: make test, make bench-cloudgoat (precision/recall against known-vulnerable scenarios), govulncheck ./....

License

Apache License 2.0.

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
path_idYesThe id from list_attack_paths.

TDQS

A4.4/5.0
Behavior4/5

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

Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safety profile is covered. The description adds valuable behavioral context: it reveals that the tool distinguishes observed evidence (kev/epss/runtime) from estimates (cvss/severity/heuristic), which is a key behavioral trait an agent needs to interpret results correctly. It doesn't describe output format, but with no output schema and read-only semantics, the provenance explanation is the most important behavioral disclosure.

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?

The description is two sentences, front-loaded with the core purpose and the full list of what's included, followed by a usage directive. Every sentence earns its place; no filler or repetition of schema details.

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?

For a read-only, single-parameter tool, the description is nearly complete. It explains what the tool returns, why to use it, and how to interpret the provenance field. The only minor gap is that it doesn't explicitly state the output format or whether the response includes remediation steps, but the description's focus on the kill chain and provenance is sufficient for an agent to select and invoke 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% and the single parameter path_id is described as 'The id from list_attack_paths.' The description doesn't add much beyond that, but the parameter is simple and self-explanatory. Baseline 3 is appropriate since the schema fully documents the parameter.

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 ('explain') and resource ('attack path'), and enumerates exactly what the full kill chain includes: every hop, relationship type, probability, provenance, and MITRE technique. It clearly distinguishes itself from sibling tools like list_attack_paths by focusing on a single route's detailed breakdown.

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?

The description explicitly says 'Use this before explaining or acting on a route' and explains why: the hop provenance tells you which parts are evidence and which are assumption. This gives clear when-to-use guidance and implicitly contrasts with list_attack_paths (which lists paths) and routes_to_target (which finds routes).

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.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior4/5

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

Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safety profile is covered. The description adds value by specifying what the summary actually surfaces – counts and reachable sensitive assets – which is behavioral information beyond the annotations. It does not contradict any annotation.

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?

The description is exactly two sentences, front-loaded with the core action and then listing the specifics. Every word earns its place – no filler, no repetition. This is a model of concise, structured documentation.

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 simple, parameterless tool with no output schema, the description is complete. It explains exactly what information the summary provides (attack route counts, asset and relationship counts, reachable sensitive assets) and when to use it. An agent would need nothing else to call this tool correctly.

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 has zero parameters, so the description has no parameter semantics to explain. Per the rubric, a zero-parameter tool gets a baseline of 4 because the schema (empty) fully covers it. The description does not need to add anything here.

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 ('Summarize') and a clear resource ('the environment'), and enumerates exactly what the summary includes: counts of open and runtime-confirmed attack routes, mapped assets and relationships, and reachable sensitive assets. This clearly distinguishes it from sibling tools like list_attack_paths or explain_attack_path, which are focused on specific enumeration or explanation rather than an overall posture snapshot.

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?

The phrase 'Start here to orient before enumerating anything' explicitly tells the agent when to invoke this tool (first, before any enumeration). This provides unambiguous usage context, even though it doesn't name alternatives – the siblings are implicitly the enumeration tools that follow. This is clear guidance on sequencing.

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.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior5/5

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

Annotations already mark this as read-only and non-destructive, and the description goes well beyond them by explaining the calibration verdicts, the difference between overall average calibration and per-score probability claims, the AUC-based discrimination output, and the interpretation constraints on the Priority order.

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

Conciseness3/5

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

The description is information-dense and front-loaded with the core purpose, but it is a single long run-on paragraph with many embedded caveats. Structuring the verdicts, rates, and discrimination guidance into separate sentences or bullets would improve 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?

With no output schema and no parameters, the description carries full responsibility for explaining the return shape. It covers the verdict values, the predicted-versus-observed rates, the discrimination AUC and interval, and the required downstream presentation caveats. Nothing essential is missing for an agent to invoke and interpret the tool correctly.

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 schema has nothing to document. The description uses the no-parameter baseline appropriately by focusing entirely on what the report contains and how to interpret it.

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 ('report') and resource ('how well the engine's probabilities have matched reality'), and enumerates the concrete outputs: verdict, predicted-versus-observed rates, and discrimination. It is clearly distinguishable from siblings like get_posture or list_attack_paths.

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 an explicit when-to-use instruction: 'Call this before quoting any score as a probability.' It also provides when-not guidance about not presenting rankings as evidence unless priorityDiscrimination says 'discriminates', and how to handle insufficient-data results.

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
appNoOptional application scope; omit for the whole environment.
limitNoHow many routes to return, priority-first.

TDQS

A4.2/5.0
Behavior4/5

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

Annotations already declare readOnly, idempotent, and non-destructive behavior. The description goes beyond annotations by warning that probabilities are expert estimates, not calibrated measurements, and by defining the meaning of score and priority.

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?

The description is compact: it states the purpose, defines the key output concepts, and adds a clearly separated important caveat. Every sentence contributes useful information without redundancy.

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?

For a list tool without an output schema, it explains the ranking, meaning of score, meaning of priority, and the trust caveat. It does not fully enumerate all possible route fields, but the essential behavior and call context are sufficiently covered.

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?

The schema already describes both parameters fully, so the description does not need to repeat them. It also does not add parameter-specific semantics beyond the schema's own descriptions, so the baseline score 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 names a specific verb ('List') and resource ('ranked routes from internet exposure to a sensitive asset'), and states the ordering principle ('highest triage priority first'). It is clearly distinguishable from siblings like explain_attack_path or get_score_trust.

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 operational guidance: call get_score_trust before presenting probabilities and prefer the ranking over absolute values. It does not explicitly contrast this tool with sibling alternatives such as routes_to_target or explain_attack_path, but the usage context is clear enough.

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
appNoOptional application scope; omit for the whole environment.

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 behavioral context beyond annotations: output includes per-change share of critical-path risk eliminated and number of routes cut, plus prioritization by choke points.

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?

The description is compact: the first sentence front-loads the core purpose and output details, and the second provides practical guidance without redundancy. Every sentence earns its place.

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 simple read-only tool with one optional parameter and no output schema, the description sufficiently explains what the tool returns and how it behaves. No critical calling information 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% for the single optional 'app' parameter, so the schema already explains its meaning. The description adds no parameter-specific detail, but the baseline of 3 is appropriate because the schema fully handles parameter semantics.

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 ('Return') and resource ('remediation plan'), and clearly defines the output as the fewest changes removing the most risk, ordered by choke points. It is conceptually distinct from siblings like list_attack_paths and explain_attack_path, which focus on attack paths rather than prioritized remediation actions.

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?

The description gives clear usage context: it is 'usually the right answer to what should we do', implying it is the go-to tool for deciding on remediation actions. It does not explicitly mention when not to use it or name alternative tools, but the use case is clearly conveyed.

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
kNoHow many distinct routes to return, best first. Fewer routes come back when fewer exist.
fromNoOptional entry-point name to start from.
targetYesSensitive asset name, e.g. 'account-admin (effective)'.

TDQS

A4.2/5.0
Behavior4/5

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

Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, covering the safety profile. The description adds meaningful behavioral context beyond annotations by explaining the choke point concept and the effect of cutting hops, which informs the agent's interpretation of results. It does not contradict annotations.

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?

The description is two sentences with no filler. The core purpose is front-loaded, and the second sentence provides the analytical value proposition. Every word contributes to understanding the tool's function and purpose.

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?

Given the moderate complexity (3 parameters, no output schema) and the annotations covering safety, the description is fairly complete. It explains the tool's purpose and the meaning of its output (routes, choke points) without explicitly detailing return structure, which is acceptable given the tool's conceptual nature and the absence of an output schema.

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 the schema fully documents each parameter (k, from, target). The tool description references 'k best distinct routes' and 'named sensitive asset', which aligns with the schema but does not add additional semantic detail beyond what the schema already provides. Baseline 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 precise action: 'Enumerate the k best distinct routes that reach a named sensitive asset.' It clearly identifies the resource (routes to a target) and the specific value it provides (answering 'how many ways in' and choke point analysis), distinguishing it from single-path views.

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?

The description explicitly contrasts with 'single-path views' and explains when this tool is valuable (multi-route choke point analysis), implying that single-path tools (like list_attack_paths or explain_attack_path) are for different use cases. However, it does not name alternatives or provide explicit exclusion conditions, so it stops short of full guidance.

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.

ParametersJSON Schema
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'.

TDQS

A4/5.0
Behavior3/5

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

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint, covering the safety profile. The description adds context about searching 'indexed assets and findings' and returning node ids, but it does not describe the exact response shape or empty-match behavior. This is adequate but not rich.

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 with no filler. The core operation and matching scope are front-loaded in the first sentence, and the practical usage guidance is in the second. Every clause earns its place.

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?

For a read-only two-parameter search tool, the description covers scope, purpose, and how results should be consumed ('node ids the other tools take'). With no output schema, slightly more detail about the result structure could help, but the description provides enough for correct invocation.

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 the query and size parameters are already well documented with examples and constraints. The description's phrase 'by name, CVE id, or keyword' is a slight restatement of the schema's field list rather than new semantic information. Baseline 3 applies since the schema does the heavy lifting.

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: 'Full-text search across indexed assets and findings by name, CVE id, or keyword.' It clearly differentiates this tool from the attack-path and posture siblings by emphasizing the lookup/translation role, and the second sentence reinforces the specific job of resolving human-mentioned names into node ids.

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?

The description explicitly tells an agent when to use this tool: 'Use it to resolve a name a human mentioned into the node ids the other tools take.' It gives clear usage context but does not name alternatives or state when-not-to-use conditions, though no search sibling exists to exclude.

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
cutsYesRelationships to remove. Use node ids from explain_attack_path steps (from/to).

TDQS

A4.4/5.0
Behavior4/5

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

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint, so the safety profile is covered. The description adds meaningful behavioral detail beyond annotations: it re-runs the whole simulation, is deterministic, and reports both route count changes and probability movement. No contradiction with annotations.

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 with no filler. The core behavior is front-loaded, the output is specified, and the usage guidance is integrated naturally without redundancy.

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?

The description covers what the tool does, what it reports, and when to use it. There is no output schema, but the description explains the key outputs. It could mention cost or performance implications of re-running the full simulation, but that is a minor gap given the annotations already cover safety.

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 baseline is 3, but the description adds value by explaining that cuts are relationships removed and that the simulation runs with those edges removed. It also reinforces the schema's instruction to use node ids from explain_attack_path steps, making parameter usage clearer.

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: 'Ask what actually happens if specific relationships are cut' and describes the concrete outputs (routes disappearing, compromise probability moving). It clearly differentiates itself from siblings by emphasizing this is a deterministic counterfactual over the real graph, not an estimate.

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?

The description explicitly says 'This is the tool to reach for before recommending a change' and frames it as settling 'would this help' instead of arguing. It does not name a specific sibling alternative, but the deterministic-versus-estimate contrast gives usable context for choosing it.

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 updates
    • First observedexplain_attack_path
    • First observedget_posture
    • First observedget_score_trust
    • First observedlist_attack_paths
    • First observedlist_fixes
    • First observedroutes_to_target
    • First observedsearch_assets
    • First observedsimulate_fix

TDQS

A4.2/5.0

Scored across 8 tools

Disambiguation4/5

Most tools have clearly distinct purposes: explain_attack_path drills into one route, list_attack_paths ranks all routes, routes_to_target enumerates routes to a specific asset, and simulate_fix tests changes. The only potential confusion is between list_attack_paths and routes_to_target, but their descriptions clarify one is a global ranked list while the other is target-specific choke-point analysis.

Naming Consistency4/5

The dominant pattern is verb_noun (explain_attack_path, get_posture, list_attack_paths, search_assets, simulate_fix). One outlier, routes_to_target, is a noun phrase rather than verb_noun, and there is minor singular/plural variation (attack_path vs attack_paths, fix vs fixes), but overall the naming is predictable and readable.

Tool Count5/5

Eight tools is well-scoped for a security analysis server. Each tool earns its place: orientation, enumeration, deep-dive, trust calibration, remediation planning, and counterfactual simulation. No tool feels redundant or missing to make the set bloated or thin.

Completeness4/5

The tool surface covers the core lifecycle: orient (get_posture, search_assets), enumerate (list_attack_paths, routes_to_target), explain (explain_attack_path), validate score trust (get_score_trust), and remediate (list_fixes, simulate_fix). A minor gap is the lack of a dedicated asset-detail or relationship-detail tool, but search_assets and explain_attack_path compensate sufficiently for common workflows.

Maintenance

ActivityActive
ResponsivenessSlow

Related MCP Connectors

Related MCP Servers