Skip to content

MCP tool reference

The guardlink-mcp server registers 28 tools and 3 resources. Everything below was read from a running guardlink 2.2.0 server over the protocol, not from its source.

Start it with guardlink-mcp or guardlink mcp — both run the same server. See guardlink-mcp for client configuration.

Field Value
Name guardlink
Version 2.2.0
Capabilities resources, tools
Tools 28
Resources 3

Returned in the initialize response and placed in the model’s context by most clients. Reproduced verbatim: this text is part of what the server makes an agent believe, so it belongs in the reference alongside the tools.

Show the instructions the server sends
GuardLink is this project's threat model: security facts developers recorded next to the code — what each component is exposed to, what mitigates it, how data flows between them — parsed into a queryable model. Ask it instead of inferring security context from the source.
WHEN TO REACH FOR WHAT
Opened a file, or about to edit one → guardlink_context(file)
Annotations declared there with line anchors, the assets they name with each
asset's immediate neighbours, open exposures, and the controls the file is
expected to uphold. An empty answer says which kind of empty it is:
scanned_without_annotations means genuinely clean; not_scanned means the parser
never read the file. Do not read them as the same thing.
About to change a shared component → guardlink_graph(from, depth, direction)
Blast radius. Walks data flows and trust boundaries — not shared threats, which
are classifications rather than couplings. Defaults (depth 2, both, summary) are
the affordable setting; detail full costs ~40% more. Check traversal.completeness:
depth_limited means more is reachable, truncated means INCOMPLETE —
frontier_unexplored says what is missing.
A scanner reported a CWE → guardlink_lookup("cwe:CWE-89")
Whether this model declares that weakness class at all, and whether affected
sites are mitigated, accepted, open or confirmed. Check external_id.declared:
false means never heard of it, which is NOT the same as declared-and-clean.
Before you finish → guardlink_validate · After a change → guardlink_diff("HEAD~1")
Cold on an unfamiliar repo → guardlink_status
Who introduced this, and did an AI co-author it → guardlink_blame(file)
WRITING ANNOTATIONS
This project has not recorded an annotation mode. Every tool response carries `mode` in its guardlink envelope, observed from the annotations themselves — read that rather than assuming.
Definitions — @asset, @threat, @control with #ids — live in .guardlink/definitions.*. Reuse
existing ids; never redefine one. Never write @accepts or @entitles: both are human
governance decisions. Found a risk with no control? @exposes plus @audit. An
entitlement claims a privilege was always allowed this, so an over-grant hides a
real escalation: propose it (guardlink_entitlement_propose) citing authz code as
file:line, and a human's acceptance writes it. Never for ownership questions (IDOR,
tenant isolation), where both peers hold the capability.
Write with guardlink_annotate_apply, not by hand: pass the source path, not the .gal.
@source is synthesised — never send one. Do send symbol:, which reanchor needs after a
refactor; omitting it means the statement covers the whole asset rather than one symbol.
READING THE ANSWERS
Every response carries a guardlink envelope: annotation_hash, git_sha, mode, root.
Identical hash means identical model — use it to tell a fresh answer from a stale one.
Anything resolving a reference reports matched_via: exact, alias or substring. A
substring match is a suggestion, not an identification. ambiguous with candidates
means several records tied and one was picked arbitrarily — re-ask precisely.
guardlink_lookup understands 32 named query forms and refuses anything else
rather than guessing. Send it a deliberately bad query to get the list.
Tool Summary
guardlink_parse Parse GuardLink annotations and return the threat model as JSON.
guardlink_status Return coverage statistics: asset/threat/control counts, unmitigated exposures, @confirmed count, coverage percentage
guardlink_validate Check annotations for syntax errors, duplicate IDs, and dangling references.
guardlink_suggest Given a file path or code diff, suggest appropriate GuardLink annotations based on code patterns, imports, and function signatures
guardlink_worklist Which code to annotate first, ranked from the code itself.
guardlink_reach Before writing an @exposes or @flows for a function: what it reaches and what reaches it, from an optional code graph (see guardlink_worklist).
guardlink_lookup Query the threat model graph.
guardlink_context Everything GuardLink knows about one file: the annotations declared there, the assets they name with each asset’s depth-1 neighbourhood, open exposures and @…
guardlink_graph Blast radius: the neighbourhood around an asset, or the path between two.
guardlink_annotate_apply Write a validated @source block into the annotation sidecar for a file.
guardlink_reanchor Find @source blocks whose recorded file:line no longer holds the symbol they name — the drift external annotations accumulate after a refactor.
guardlink_threat_report Generate an AI threat report using a security framework (STRIDE, DREAD, PASTA, attacker, rapid, general).
guardlink_annotate Build an annotation prompt with project context, GuardLink reference docs, and GAL syntax guidelines.
guardlink_report Generate a markdown threat model report with Mermaid diagram.
guardlink_dashboard Generate an interactive HTML threat model dashboard with diagrams, charts, code annotations, and heatmap.
guardlink_sarif Export findings as SARIF 2.1.0 for GitHub Advanced Security, VS Code, and other SARIF consumers.
guardlink_diff Compare the current threat model against a git ref (commit, branch, tag).
guardlink_paths Undefended routes through the flow graph: every path from a point where data enters the system to a point where it leaves, running through a declared asset w…
guardlink_threat_reports List saved AI threat reports from .guardlink/threat-reports/ (and legacy .guardlink/analyses/).
guardlink_sync Sync all agent instruction files (CLAUDE.md, .cursorrules, etc.) with the current threat model.
guardlink_clear Remove all GuardLink annotations from source files.
guardlink_unannotated List source files that have no GuardLink annotations.
guardlink_review_list List all unmitigated exposures eligible for governance review, sorted by severity.
guardlink_review_accept Record a governance decision for an unmitigated exposure.
guardlink_entitlement_propose Propose an @entitles claim (an actor is entitled to a capability BY DESIGN) into .guardlink/entitlement-proposals.json.
guardlink_entitlement_list List entitlement proposals and their decisions from .guardlink/entitlement-proposals.json.
guardlink_blame Who introduced the code beneath each claim, who declared it, who declared its fix, and which AI tool co-authored those commits — read from git (blame, log -L…
guardlink_workspace_info Get workspace configuration for multi-repo threat modeling.

Answer a question from the model, or from an installed code graph, without changing either.

Parse GuardLink annotations and return the threat model as JSON. Omits unannotated_files by default — on a large repo that list is ~90% of the payload and carries no threat-model information; use guardlink_unannotated, which exists for exactly that data, or set include_unannotated. Prefer guardlink_context for a single file and guardlink_lookup for a specific question; this is the expensive call for when you genuinely need the whole model.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
compact boolean no false Return the compact serialization: stats, assets, ALL unmitigated exposures, threat severity index, flows, boundaries and data classifications, with descriptions capped. Omits resolved mitigations, working controls, comments and validations.
include_unannotated boolean no false Include the unannotated_files list. Off by default; guardlink_unannotated is the dedicated tool for it.
Input schema for guardlink_parse
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"compact": {
"default": false,
"type": "boolean",
"description": "Return the compact serialization: stats, assets, ALL unmitigated exposures, threat severity index, flows, boundaries and data classifications, with descriptions capped. Omits resolved mitigations, working controls, comments and validations."
},
"include_unannotated": {
"default": false,
"type": "boolean",
"description": "Include the unannotated_files list. Off by default; guardlink_unannotated is the dedicated tool for it."
}
}
}

Return coverage statistics: asset/threat/control counts, unmitigated exposures, @confirmed count, coverage percentage

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
Input schema for guardlink_status
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
}
}
}

Everything GuardLink knows about one file: the annotations declared there, the assets they name with each asset’s depth-1 neighbourhood, open exposures and @confirmed findings, controls the file upholds, and its @assumes/@handles/@owns. Call this when you open or are about to edit a file. Accepts the source path or, in external mode, the .gal path that annotates it. An empty result is explicit about WHY: scanned_without_annotations means the file is genuinely clean, not_scanned means the parser never read it, not_found means nothing is there — do not read them as the same answer. Reports origin_file for annotations that already exist, not a destination for a new one. In external mode a new sidecar goes at .guardlink/annotations/<source path>.gal — the source path mirrored, with .gal appended.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
file string yes — File to describe. Absolute, relative or ./-prefixed; resolved against root. In external mode the .gal path resolves to the source file it annotates.
line number no — Optional line number. Narrows to the enclosing symbol where the annotation recorded one (@source symbol:). Reports symbol_scope.applied = “unavailable” when no annotation for the file records a symbol, rather than silently returning the whole file.
Input schema for guardlink_context
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"file": {
"type": "string",
"description": "File to describe. Absolute, relative or ./-prefixed; resolved against root. In external mode the .gal path resolves to the source file it annotates."
},
"line": {
"type": "number",
"description": "Optional line number. Narrows to the enclosing symbol where the annotation recorded one (@source symbol:). Reports symbol_scope.applied = \"unavailable\" when no annotation for the file records a symbol, rather than silently returning the whole file."
}
},
"required": [
"file"
]
}

Query the threat model graph. Reaches every relation type the model carries: assets, threats, controls, mitigations, exposures, confirmed, acceptances, transfers, flows, boundaries, validations, audits, ownership, data classification, assumptions, actors, entitlements, reaches (@agents/@reaches), effects, gates, shields, features, comments and cross-repo refs. Examples: “threats for #auth”, “owner of #api”, “handles pii”, “assumptions for #api”, “flows into Scanner”, “unmitigated”, “actors”, “entitlements for #ns-admin”, “unentitled reaches”, “effects for #db”. A query that is not one of the supported forms returns no_match listing them — it is never answered by guesswork.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
query string yes — A supported query form: “unmitigated”, “confirmed”, “features”, “asset <id>”, “threat <id>”, “control <id>”, “threats for <asset>”, “controls for <asset>”, “exposures for <asset>”, “mitigations for <asset>”, “flows into <asset>”, “flows from <asset>”, “boundary for <asset>”, “owner of <asset>”, “handles <pii|phi|financial|secrets|internal|public>”, “handles for <asset>”, “assumptions for <asset>”, “audits [for <asset>]”, “validations for <asset-or-control>”, “acceptances [for <asset>]”, “transfers [for <threat-or-asset>]”, “actors”, “entitlements [for <actor>]”, “reaches [for <actor>]”, “agents [for <actor>]”, “unentitled reaches [for <actor>]”, “effects [for <asset>]”, “gates [for <asset>]”, “ungated effects [for <asset>]”, “comments [for <file-or-asset>]”, “shields [for <file-or-asset>]”, or a bare identifier. TWO DIFFERENT REF QUERIES, do not confuse them: “cwe:CWE-89” / “CWE-89” / “owasp:A03” asks about external identifiers declared on threats — the scanner bridge, and returns external_id.declared so you can tell ‘never heard of this weakness’ from ‘declared, nothing exposed’; “cross-repo refs” asks about sibling-repo tags from workspace.yaml and is unrelated. Every entitlements row carries inert: an uncited claim is carried and visible but cannot demote a finding (actor-entitlement design §3.4). “unentitled reaches” is can minus may: each @agents/@reaches capability no cited @entitles for the same actor and capability (and the same asset, when the reach names one) covers, with near_misses saying why an entitlement that almost matched does not count. “ungated effects” is every write, delete, execute, spend or notify @effects no @gates covers, with gate_near_misses saying why a gate on the same asset does not count. @comment and @shield record no asset, so scoping them by an asset joins by co-location (same file) and the result says so. Free-form questions are not parsed.
Input schema for guardlink_lookup
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"query": {
"type": "string",
"description": "A supported query form: \"unmitigated\", \"confirmed\", \"features\", \"asset <id>\", \"threat <id>\", \"control <id>\", \"threats for <asset>\", \"controls for <asset>\", \"exposures for <asset>\", \"mitigations for <asset>\", \"flows into <asset>\", \"flows from <asset>\", \"boundary for <asset>\", \"owner of <asset>\", \"handles <pii|phi|financial|secrets|internal|public>\", \"handles for <asset>\", \"assumptions for <asset>\", \"audits [for <asset>]\", \"validations for <asset-or-control>\", \"acceptances [for <asset>]\", \"transfers [for <threat-or-asset>]\", \"actors\", \"entitlements [for <actor>]\", \"reaches [for <actor>]\", \"agents [for <actor>]\", \"unentitled reaches [for <actor>]\", \"effects [for <asset>]\", \"gates [for <asset>]\", \"ungated effects [for <asset>]\", \"comments [for <file-or-asset>]\", \"shields [for <file-or-asset>]\", or a bare identifier. TWO DIFFERENT REF QUERIES, do not confuse them: \"cwe:CWE-89\" / \"CWE-89\" / \"owasp:A03\" asks about external identifiers declared on threats — the scanner bridge, and returns external_id.declared so you can tell 'never heard of this weakness' from 'declared, nothing exposed'; \"cross-repo refs\" asks about sibling-repo tags from workspace.yaml and is unrelated. Every entitlements row carries inert: an uncited claim is carried and visible but cannot demote a finding (actor-entitlement design §3.4). \"unentitled reaches\" is can minus may: each @agents/@reaches capability no cited @entitles for the same actor and capability (and the same asset, when the reach names one) covers, with near_misses saying why an entitlement that almost matched does not count. \"ungated effects\" is every write, delete, execute, spend or notify @effects no @gates covers, with gate_near_misses saying why a gate on the same asset does not count. @comment and @shield record no asset, so scoping them by an asset joins by co-location (same file) and the result says so. Free-form questions are not parsed."
}
},
"required": [
"query"
]
}

Blast radius: the neighbourhood around an asset, or the path between two. Traversal walks the ASSET plane only — @flows (directed), @boundary (undirected, crossable either way) and @transfers (directed). It does NOT hop through shared threats: #path-traversal alone is declared on 10 assets here, so crossing threats would make depth 2 reach most of the graph and depth would stop meaning anything. Threats and controls are still returned for every asset in the neighbourhood, they just are not transited through. Returns a filtered ThreatModel, so the result is a model like any other — minus unannotated_files, which is a whole-repo file inventory rather than subgraph data and is omitted here for the same reason guardlink_parse omits it; guardlink_unannotated owns that list. Use format: “mermaid” for a diagram, or path_to for a route between two assets.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
from string yes — Asset to start from. Resolved exactly as “asset X” is — same tiers, same ambiguity reporting. Later hops match canonical identity only, never fuzzily.
path_to string no — If set, return the shortest path from from to this asset instead of a neighbourhood. Directed edges are followed forwards; boundaries either way. An unresolvable endpoint and a genuinely disconnected pair are reported differently.
depth number no 2 Hops from from. 0 is the asset alone, 1 matches what “asset X” reports. Clamped to 10. Check traversal.completeness on the result: “complete” means there is nothing more to find at any depth, “depth_limited” means raising this would return more (and frontier_unexplored names what is immediately beyond), “truncated” means the depth-10 ceiling cut it short and the result is INCOMPLETE.
direction string, "in" | "out" | "both" no "both" Which way directed edges are followed. Boundaries are undirected and are crossed in every direction regardless.
kinds string[] no — Relation arrays to keep in the returned model. Filters the OUTPUT, not the traversal — excluding “flows” still walks flows, it just omits them from the result. Assets, threats and controls are always kept as the node vocabulary.
format string, "json" | "mermaid" no "json" json returns the filtered ThreatModel plus traversal detail; mermaid renders it with the same generator the dashboard uses.
detail string, "summary" | "full" no "summary" How much per-node detail to return. summary (default) drops description prose and compacts location to an at: "file:line" string — measured at roughly 40% of the payload, and the graph is the same graph either way: identical nodes, identical edges. full returns the complete records and is the only mode whose model is a valid ThreatModel.
feature string no — Restrict to one feature before traversing.
file string no — Restrict to annotations declared in one file before traversing.
Input schema for guardlink_graph
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"from": {
"type": "string",
"description": "Asset to start from. Resolved exactly as \"asset X\" is — same tiers, same ambiguity reporting. Later hops match canonical identity only, never fuzzily."
},
"path_to": {
"type": "string",
"description": "If set, return the shortest path from `from` to this asset instead of a neighbourhood. Directed edges are followed forwards; boundaries either way. An unresolvable endpoint and a genuinely disconnected pair are reported differently."
},
"depth": {
"default": 2,
"type": "number",
"description": "Hops from `from`. 0 is the asset alone, 1 matches what \"asset X\" reports. Clamped to 10. Check `traversal.completeness` on the result: \"complete\" means there is nothing more to find at any depth, \"depth_limited\" means raising this would return more (and `frontier_unexplored` names what is immediately beyond), \"truncated\" means the depth-10 ceiling cut it short and the result is INCOMPLETE."
},
"direction": {
"default": "both",
"type": "string",
"enum": [
"in",
"out",
"both"
],
"description": "Which way directed edges are followed. Boundaries are undirected and are crossed in every direction regardless."
},
"kinds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Relation arrays to keep in the returned model. Filters the OUTPUT, not the traversal — excluding \"flows\" still walks flows, it just omits them from the result. Assets, threats and controls are always kept as the node vocabulary."
},
"format": {
"default": "json",
"type": "string",
"enum": [
"json",
"mermaid"
],
"description": "json returns the filtered ThreatModel plus traversal detail; mermaid renders it with the same generator the dashboard uses."
},
"detail": {
"default": "summary",
"type": "string",
"enum": [
"summary",
"full"
],
"description": "How much per-node detail to return. summary (default) drops `description` prose and compacts `location` to an `at: \"file:line\"` string — measured at roughly 40% of the payload, and the graph is the same graph either way: identical nodes, identical edges. full returns the complete records and is the only mode whose `model` is a valid ThreatModel."
},
"feature": {
"type": "string",
"description": "Restrict to one feature before traversing."
},
"file": {
"type": "string",
"description": "Restrict to annotations declared in one file before traversing."
}
},
"required": [
"from"
]
}

Undefended routes through the flow graph: every path from a point where data enters the system to a point where it leaves, running through a declared asset with no control on it. Derived from @flows and @mitigates with no model in the loop, so every hop cites a real annotation location. Entry and exit are structural — an endpoint present in the graph but not declared as an @asset, with no inbound flow (entry) or no outbound flow (exit). Ask this instead of tracing reachability by reading source.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
include_mitigated boolean no false Include routes a control already covers
boundary_only boolean no false Only routes that cross a declared @boundary
Input schema for guardlink_paths
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"include_mitigated": {
"default": false,
"type": "boolean",
"description": "Include routes a control already covers"
},
"boundary_only": {
"default": false,
"type": "boolean",
"description": "Only routes that cross a declared @boundary"
}
}
}

Who introduced the code beneath each claim, who declared it, who declared its fix, and which AI tool co-authored those commits — read from git (blame, log -L, Co-Authored-By / Assisted-by trailers); nothing is written. AI credit is DECLARED by commit trailers, never detected from code: a commit with no trailer stays human. Pass file to narrow to one file. Entries carry the ledger claim key, so they join to .guardlink/verified.json. Outside a git checkout every entry is status no-git; uncommitted means the line has changes git has not seen; shallow means every introduction is a lower bound.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
file string no — Optional file: only claims declared in, or anchored to, it. Absolute, relative or ./-prefixed; resolved against root.
Input schema for guardlink_blame
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"file": {
"type": "string",
"description": "Optional file: only claims declared in, or anchored to, it. Absolute, relative or ./-prefixed; resolved against root."
}
}
}

List source files that have no GuardLink annotations. Useful for identifying coverage gaps. Not all files need annotations — only those touching security boundaries (endpoints, auth, data access, I/O, crypto).

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
Input schema for guardlink_unannotated
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
}
}
}

Get workspace configuration for multi-repo threat modeling. Returns workspace name, this repo’s identity, sibling repos, and their tag prefixes. Use this to understand cross-repo references when writing annotations. Returns null fields if the repo is not part of a workspace.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
Input schema for guardlink_workspace_info
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
}
}
}

Which code to annotate first, ranked from the code itself. Needs an optional code graph (codegraph-mcp or bravos installed, and this repository indexed); nothing is installed or built for you. Returns every entry point the graph ranked — each route handler with its METHOD path, file:line and graph address, the sink classes it reaches through the call graph (data_layer, exec, template, network, filesystem, crypto_secrets) with an example sink, whether the threat model already covers it (none / file / handler), and — when the graph classifies route access — each route access level (public / authenticated / elevated / unknown, with basis and the guard it rests on) — handlers not yet annotated first, heaviest reach first. Access is carried only when code_graph.auth_verdict is classified; otherwise access_withheld_reason says why, and an unknown level is unknown, never public. READ code_graph.status FIRST: anything but available means there is no worklist, and note says why. withheld_reason means the graph judged its own call resolution too thin to rank, so the list is empty ON PURPOSE — never read that as a small attack surface. Pass a handler address to guardlink_reach before writing its @exposes or @flows.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
file string no — Keep only handlers whose file path contains this text
limit integer, ≥ 1, ≤ 1000 no 25 Entries to return (default 25); totals are counted before it
Input schema for guardlink_worklist
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"file": {
"description": "Keep only handlers whose file path contains this text",
"type": "string"
},
"limit": {
"default": 25,
"description": "Entries to return (default 25); totals are counted before it",
"type": "integer",
"minimum": 1,
"maximum": 1000
}
}
}

Before writing an @exposes or @flows for a function: what it reaches and what reaches it, from an optional code graph (see guardlink_worklist). Returns the sink classes the function transitively reaches through the call graph, each with its shallowest depth, an example sink call and the path to it, plus liveness — live_via_route (a route handler reaches it; the entries carry the METHOD path), live_via_entry (only main does), not_reached, or unknown. READ unknown AS UNKNOWN, never as dead, and not_reached as “no RECOGNISED entry point”, not as unreachable. sinks_withheld_reason means the graph judged its call resolution too thin: no sinks are listed on purpose. A name matching several functions is refused with candidates — re-ask with one.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
symbol string, length ≥ 1, length ≤ 1000 yes — The function, addressed exactly: a graph address from guardlink_worklist (api/users.py::delete_user), a qualified name, or a bare name
Input schema for guardlink_reach
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"symbol": {
"type": "string",
"minLength": 1,
"maxLength": 1000,
"description": "The function, addressed exactly: a graph address from guardlink_worklist (`api/users.py::delete_user`), a qualified name, or a bare name"
}
},
"required": [
"symbol"
]
}

Validate what is written down, and fix anchors that drifted off their symbol.

Check annotations for syntax errors, duplicate IDs, and dangling references. Returns structured error list. With code_graph: true, also checks each declared @boundary against an optional code graph — a boundary whose description says its inner side needs a login (or an admin check) while a route there is classified public (boundary-access-contradicted) or could not be classified (boundary-access-unknown), a route handler reaching a sink with no boundary to the outside (boundary-missing), a boundary no flow crosses and no entry point reaches (boundary-unused). Those are warnings about claims to verify, never findings; boundary_checks says whether the graph was usable.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
code_graph boolean no false Also check declared boundaries against a code graph, when one is installed and built for this repository
Input schema for guardlink_validate
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"code_graph": {
"default": false,
"description": "Also check declared boundaries against a code graph, when one is installed and built for this repository",
"type": "boolean"
}
}
}

Find @source blocks whose recorded file:line no longer holds the symbol they name — the drift external annotations accumulate after a refactor. Reports and proposes; it does not rewrite anything unless you pass apply: true, and it never invents an anchor for a symbol that has disappeared.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
apply boolean no false Rewrite @source lines to the proposed positions. Only blocks whose symbol was found elsewhere are moved; a vanished symbol is always left for a human.
Input schema for guardlink_reanchor
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"apply": {
"default": false,
"type": "boolean",
"description": "Rewrite @source lines to the proposed positions. Only blocks whose symbol was found elsewhere are moved; a vanished symbol is always left for a human."
}
}
}

Propose and apply annotations. These are the tools that change source.

Given a file path or code diff, suggest appropriate GuardLink annotations based on code patterns, imports, and function signatures

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
file string no — File path relative to root to analyze
diff string no — Git diff text to analyze for new code needing annotations
Input schema for guardlink_suggest
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"file": {
"type": "string",
"description": "File path relative to root to analyze"
},
"diff": {
"type": "string",
"description": "Git diff text to analyze for new code needing annotations"
}
}
}

Build an annotation prompt with project context, GuardLink reference docs, and GAL syntax guidelines. The calling agent should use this prompt to read source files and add security annotations directly. Returns the prompt text — the agent should then read files, decide annotation placement, and write comments.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
prompt string yes — Annotation instructions (e.g., “annotate auth endpoints for OWASP Top 10”)
mode string, "inline" | "external" no "inline" Annotation placement mode — inline (default) or external (externalized .gal files)
playbook string, "map" | "exploitable" | "chains" | "diff" | "coverage" | "verify" no — The method: map (architecture first, no exposures), exploitable (default: claim only what an attacker can reach), chains (follow flows from open exposures), diff (changed files only), coverage (unannotated files), verify (re-check existing claims). Inferred from the prompt when omitted.
code_graph boolean no true Add a ranked entry-point worklist when a code graph is installed and built for this repository (false to leave it out). Without a graph the prompt is unchanged either way.
Input schema for guardlink_annotate
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"prompt": {
"type": "string",
"description": "Annotation instructions (e.g., \"annotate auth endpoints for OWASP Top 10\")"
},
"mode": {
"default": "inline",
"type": "string",
"enum": [
"inline",
"external"
],
"description": "Annotation placement mode — inline (default) or external (externalized .gal files)"
},
"playbook": {
"description": "The method: map (architecture first, no exposures), exploitable (default: claim only what an attacker can reach), chains (follow flows from open exposures), diff (changed files only), coverage (unannotated files), verify (re-check existing claims). Inferred from the prompt when omitted.",
"type": "string",
"enum": [
"map",
"exploitable",
"chains",
"diff",
"coverage",
"verify"
]
},
"code_graph": {
"default": true,
"description": "Add a ranked entry-point worklist when a code graph is installed and built for this repository (false to leave it out). Without a graph the prompt is unchanged either way.",
"type": "boolean"
}
},
"required": [
"prompt"
]
}

Write a validated @source block into the annotation sidecar for a file. Unlike guardlink_annotate — which returns a prompt for you to act on — this writes the annotations itself, into .guardlink/annotations/, never into source. Rejected with the reason, before anything touches disk: a line the parser cannot read, a #reference that is not declared in .guardlink/definitions.*, a source file that does not exist, and @accepts or @entitles, which are human governance decisions — for an entitlement use guardlink_entitlement_propose, which files it for human acceptance instead of writing it. Idempotent: re-applying the same block is a no-op, not a duplicate. Definitions come first — if you need a new asset, threat or control, declare it before you reference it, or pass allow_undeclared_refs to write ahead of the definition and get a warning instead.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
file string yes — Source file the annotations describe. Must exist — the sidecar is named after it, and one for a missing file puts annotations into the model that describe nothing. The sidecar path is derived from it; you do not choose where it is written.
line number yes — Line in that file the block anchors to
symbol string no — Enclosing symbol name. Strongly recommended — it is what lets guardlink_reanchor detect drift after a refactor.
annotations string[] yes — Raw GAL lines with no comment prefix, e.g. [‘@exposes #api to #sqli [critical] – “concatenated”’]. Do NOT include @source; the header is generated.
dry_run boolean no false Validate and return the diff without writing
allow_undeclared_refs boolean no false Write even though a #reference is not declared yet, returning it in warnings. For a deliberate forward reference when the definition is coming; the default rejects, because an undeclared reference is usually a typo.
Input schema for guardlink_annotate_apply
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"file": {
"type": "string",
"description": "Source file the annotations describe. Must exist — the sidecar is named after it, and one for a missing file puts annotations into the model that describe nothing. The sidecar path is derived from it; you do not choose where it is written."
},
"line": {
"type": "number",
"description": "Line in that file the block anchors to"
},
"symbol": {
"type": "string",
"description": "Enclosing symbol name. Strongly recommended — it is what lets guardlink_reanchor detect drift after a refactor."
},
"annotations": {
"type": "array",
"items": {
"type": "string"
},
"description": "Raw GAL lines with no comment prefix, e.g. ['@exposes #api to #sqli [critical] -- \"concatenated\"']. Do NOT include @source; the header is generated."
},
"dry_run": {
"default": false,
"type": "boolean",
"description": "Validate and return the diff without writing"
},
"allow_undeclared_refs": {
"default": false,
"type": "boolean",
"description": "Write even though a #reference is not declared yet, returning it in warnings. For a deliberate forward reference when the definition is coming; the default rejects, because an undeclared reference is usually a typo."
}
},
"required": [
"file",
"line",
"annotations"
]
}

Remove all GuardLink annotations from source files. Use –dry-run to preview without modifying files. WARNING: destructive operation — requires explicit user confirmation before calling without dry-run.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
dry_run boolean no true If true, only show what would be removed
include_definitions boolean no false Also clear .guardlink/definitions files
Input schema for guardlink_clear
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"dry_run": {
"default": true,
"type": "boolean",
"description": "If true, only show what would be removed"
},
"include_definitions": {
"default": false,
"type": "boolean",
"description": "Also clear .guardlink/definitions files"
}
}
}

Sync all agent instruction files (CLAUDE.md, .cursorrules, etc.) with the current threat model. Injects live asset/threat/control IDs, open exposures, and data flows so every coding agent knows the current security posture. Run after adding or changing annotations.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
Input schema for guardlink_sync
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
}
}
}

Project the model into a report, a diagram, SARIF, or a diff.

Generate a markdown threat model report with Mermaid diagram. Also writes threat-model.json alongside. With agents: true, writes only the agent threat model (reach map, unentitled reaches, ungated mutations, OWASP LLM mapping) and no JSON.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
output string no — Output filename (default: threat-model.md, or threat-model-agents.md with agents)
agents boolean no false Write only the agent threat model, the same section the full report carries
Input schema for guardlink_report
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"output": {
"type": "string",
"description": "Output filename (default: threat-model.md, or threat-model-agents.md with agents)"
},
"agents": {
"default": false,
"type": "boolean",
"description": "Write only the agent threat model, the same section the full report carries"
}
}
}

Generate an interactive HTML threat model dashboard with diagrams, charts, code annotations, and heatmap.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
output string no "threat-dashboard.html" Output filename (default: threat-dashboard.html)
Input schema for guardlink_dashboard
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"output": {
"default": "threat-dashboard.html",
"type": "string",
"description": "Output filename (default: threat-dashboard.html)"
}
}
}

Export findings as SARIF 2.1.0 for GitHub Advanced Security, VS Code, and other SARIF consumers. profile “github” (default) is the code-scanning export; “pentest” also appends covered exposures, boundary claims and the hypothesis ledger’s state, after the github results.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
output string no "guardlink.sarif.json" Output filename (default: guardlink.sarif.json)
profile string, "github" | "pentest" no "github" github (default) or pentest
Input schema for guardlink_sarif
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"output": {
"default": "guardlink.sarif.json",
"type": "string",
"description": "Output filename (default: guardlink.sarif.json)"
},
"profile": {
"default": "github",
"type": "string",
"enum": [
"github",
"pentest"
],
"description": "github (default) or pentest"
}
}
}

Compare the current threat model against a git ref (commit, branch, tag). Shows added/removed/changed annotations, new unmitigated exposures.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
ref string no "HEAD~1" Git ref to compare against (e.g. HEAD~1, main, v1.0)
Input schema for guardlink_diff
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"ref": {
"default": "HEAD~1",
"type": "string",
"description": "Git ref to compare against (e.g. HEAD~1, main, v1.0)"
}
}
}

Generate an AI threat report using a security framework (STRIDE, DREAD, PASTA, attacker, rapid, general). If an LLM API key is set in environment, runs analysis internally and saves result. If no API key is set, returns the framework prompt and serialized threat model for the calling agent to analyze directly — write the result as markdown to .guardlink/threat-reports/.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
framework string, "stride" | "dread" | "pasta" | "attacker" | "rapid" | "general" no "general" Analysis framework
provider string no — LLM provider: anthropic, openai, google, openrouter, deepseek (auto-detected from env)
model string no — Model name override
custom_prompt string no — Custom analysis prompt to replace the framework header
web_search boolean no — Enable web search grounding for real-time vulnerability intelligence (OpenAI)
thinking boolean no — Enable extended thinking / reasoning mode (Anthropic, DeepSeek)
Input schema for guardlink_threat_report
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"framework": {
"default": "general",
"type": "string",
"enum": [
"stride",
"dread",
"pasta",
"attacker",
"rapid",
"general"
],
"description": "Analysis framework"
},
"provider": {
"type": "string",
"description": "LLM provider: anthropic, openai, google, openrouter, deepseek (auto-detected from env)"
},
"model": {
"type": "string",
"description": "Model name override"
},
"custom_prompt": {
"type": "string",
"description": "Custom analysis prompt to replace the framework header"
},
"web_search": {
"type": "boolean",
"description": "Enable web search grounding for real-time vulnerability intelligence (OpenAI)"
},
"thinking": {
"type": "boolean",
"description": "Enable extended thinking / reasoning mode (Anthropic, DeepSeek)"
}
}
}

List saved AI threat reports from .guardlink/threat-reports/ (and legacy .guardlink/analyses/). Returns filename, framework, timestamp, and model used.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
Input schema for guardlink_threat_reports
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
}
}
}

Review unmitigated exposures and propose entitlements. Acceptance stays with a human at a terminal.

List all unmitigated exposures eligible for governance review, sorted by severity. Returns exposure IDs, details, and severity. Use guardlink_review_accept to record decisions. IMPORTANT: Acceptance decisions require explicit human confirmation — do not accept exposures without asking the user first.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
severity string no — Filter by severity: “critical,high” etc.
Input schema for guardlink_review_list
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"severity": {
"description": "Filter by severity: \"critical,high\" etc.",
"type": "string"
}
}
}

Record a governance decision for an unmitigated exposure. Writes @accepts + @audit (for accept) or @audit (for remediate) directly into the source file. IMPORTANT: This modifies source files, and an acceptance removes the exposure from the SARIF a pentest reads — so it stops the risk from ever being TESTED, not just reported. Only call after explicit human confirmation, and pass that human’s name as by: an acceptance with nobody’s name on it is refused, and nothing is defaulted — this tool will not read a name off the local git config on a human’s behalf. What it writes is an @accepts annotation in this repository’s code, which is the annotation register and not the server decision log. An acceptance also expires (until), and its justification must be a reason rather than a category — the rule is enforced by the writer, so a short one is an error and not a warning.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
exposure_id string yes — Exposure ID from guardlink_review_list
decision string, "accept" | "remediate" | "skip" yes — accept = risk acknowledged; remediate = planned fix; skip = no action
justification string yes — Required explanation. For accept it must be a real reason (minimum length is enforced): what compensates for the risk, who is exposed, what would change the answer.
by string no — REQUIRED for accept: the name of the HUMAN who made this decision. Never your own name — you are not the decider, and a decision recorded under an agent’s name is worse than one recorded under none.
until string no — REQUIRED for accept: the acceptance horizon as YYYY-MM-DD. Not defaulted — it used to fall back to the project CEILING, so the cheapest call took the longest lease available. Ask the human how long they are signing for. An acceptance stops covering anything after this date.
Input schema for guardlink_review_accept
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"exposure_id": {
"type": "string",
"description": "Exposure ID from guardlink_review_list"
},
"decision": {
"type": "string",
"enum": [
"accept",
"remediate",
"skip"
],
"description": "accept = risk acknowledged; remediate = planned fix; skip = no action"
},
"justification": {
"type": "string",
"description": "Required explanation. For accept it must be a real reason (minimum length is enforced): what compensates for the risk, who is exposed, what would change the answer."
},
"by": {
"description": "REQUIRED for accept: the name of the HUMAN who made this decision. Never your own name — you are not the decider, and a decision recorded under an agent's name is worse than one recorded under none.",
"type": "string"
},
"until": {
"description": "REQUIRED for accept: the acceptance horizon as YYYY-MM-DD. Not defaulted — it used to fall back to the project CEILING, so the cheapest call took the longest lease available. Ask the human how long they are signing for. An acceptance stops covering anything after this date.",
"type": "string"
}
},
"required": [
"exposure_id",
"decision",
"justification"
]
}

Propose an @entitles claim (an actor is entitled to a capability BY DESIGN) into .guardlink/entitlement-proposals.json. This writes NOTHING to source: a human accepts the proposal with “guardlink entitle”, and only that writes the annotation. Cite the authorization code as file:line in the rationale — without a citation the entitlement is inert and will never affect triage. Never propose one for an ownership question (IDOR, tenant/namespace isolation, CWE-639/862/863): both peers hold the capability, so an entitlement cannot say whose object it was. When unsure which role the code requires, under-grant or write @exposes + @audit instead.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
actor string yes — Actor ref — #id of a declared @actor, or its declared name
capability string yes — Capability — one identifier, never prose (e.g. configure-archival-destination). It is the join key.
asset string no — Optional “on <asset>” context ref
threat string no — Threat class this claim is meant to answer for — supply it, it is what enables the ownership-class check
rationale string yes — Why this is by design. MUST contain a file:line pointer at the authz check, e.g. “By design: namespace config. Authz: common/api/metadata.go:189”
file string yes — Repo-relative source file the accepted @entitles should be written to
line integer, > 0, ≤ 9007199254740991 yes — 1-indexed anchor line in file — normally the @exposes line the claim answers for
proposed_by string no — Agent or person filing this, e.g. “claude-code”
Input schema for guardlink_entitlement_propose
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"actor": {
"type": "string",
"description": "Actor ref — #id of a declared @actor, or its declared name"
},
"capability": {
"type": "string",
"description": "Capability — one identifier, never prose (e.g. configure-archival-destination). It is the join key."
},
"asset": {
"description": "Optional \"on <asset>\" context ref",
"type": "string"
},
"threat": {
"description": "Threat class this claim is meant to answer for — supply it, it is what enables the ownership-class check",
"type": "string"
},
"rationale": {
"type": "string",
"description": "Why this is by design. MUST contain a file:line pointer at the authz check, e.g. \"By design: namespace config. Authz: common/api/metadata.go:189\""
},
"file": {
"type": "string",
"description": "Repo-relative source file the accepted @entitles should be written to"
},
"line": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991,
"description": "1-indexed anchor line in `file` — normally the @exposes line the claim answers for"
},
"proposed_by": {
"description": "Agent or person filing this, e.g. \"claude-code\"",
"type": "string"
}
},
"required": [
"actor",
"capability",
"rationale",
"file",
"line"
]
}

List entitlement proposals and their decisions from .guardlink/entitlement-proposals.json. Use it to see what has been proposed, what a human accepted, and what was rejected or deferred — a rejected claim should not be re-filed, and an accepted one is already in source. Accepting is not available to agents: it is a human decision made with “guardlink entitle”.

Task support: forbidden

Parameter Type Required Default Description
root string no "." Project root directory
status string no — Filter by status: “proposed”, “accepted,rejected”, etc.
Input schema for guardlink_entitlement_list
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"root": {
"default": ".",
"type": "string",
"description": "Project root directory"
},
"status": {
"description": "Filter by status: \"proposed\", \"accepted,rejected\", etc.",
"type": "string"
}
}
}
URI Name Description
guardlink://model threat-model Full ThreatModel JSON for the current project
guardlink://definitions definitions All defined assets, threats, and controls with their IDs
guardlink://unmitigated unmitigated List of unmitigated exposures — assets exposed to threats with no @mitigates or @accepts