Workspace¶
The workspace holds named datasets in memory. A dataset is one loaded inventory map, findings report or scanner output, or the result of a live collection. Derived structures (lookup indexes, the NetworkX graph, the dependency graph, centrality, the RDF ontology, RAG chunks) are built on first use and cached per dataset, and every mutation drops the cache and bumps the dataset's version. At most 16 datasets are kept; adding a 17th evicts the oldest one that is not active. Names must match ^[A-Za-z0-9_.\-]{1,64}$. A tool that creates a dataset under a name that is already loaded (load_dataset, snapshot_dataset, ingest_reports with new_dataset, the live tools) fails before doing any work unless it is called with replace: true; the error suggests a free name. Files are read only inside the allowed roots and written only under the output directory, both shown by workspace_status.
workspace_status¶
Category workspace · sensitivity internal · capabilities read_state · readOnly true, destructive false, idempotent true, openWorld false · outputSchema yes · timeout: layer default (300 s)
Shows every loaded dataset, which one is active, and the directories file tools may use. Call it first. With nothing loaded it adds next_steps:
Arguments: none.
Called with the arguments below, workspace_status returned this structuredContent.
{
"active_dataset": null,
"datasets": [],
"allowed_roots": [
"<estate>"
],
"output_dir": "<estate>/out",
"providers_configured": [
"aws"
],
"next_steps": [
"load_dataset(path='<dir with inventory-map.json or findings.json>')",
"map_inventory(providers=['aws']) to collect live data (needs credentials)"
]
}
After loading, each dataset appears in the compact form also used by list_datasets, and a link to cloudg://workspace is attached:
Called with the arguments below, workspace_status returned this structuredContent.
{
"active_dataset": "sample",
"datasets": [
{
"name": "sample",
"kind": "inventory",
"source": "<estate>/inventory",
"loaded_at": "2026-10-09T14:25:11+00:00",
"version": 0,
"providers": [
"aws",
"... 2 more"
],
"assets": 44,
"edges": 56,
"findings": 12,
"compliance_results": 8,
"cached": {
"graph": false,
"dependency_graph": false,
"ontology": false,
"centrality": false,
"rag_entity": false
}
},
"... 1 more"
],
"allowed_roots": [
"<estate>"
],
"output_dir": "<estate>/out",
"providers_configured": [
"aws"
]
}
The result also carried these resource links in content:
[
{
"type": "resource_link",
"uri": "cloudg://workspace",
"name": "workspace",
"title": "Workspace status",
"mimeType": "application/json"
}
]
Fields: active_dataset (name or null); datasets[] with name, kind (inventory, report, generic, live, snapshot, or a scanner name), source (file path, live, inline, ingest or snapshot of X), loaded_at, version, providers, counts of assets, edges, findings and compliance_results, and cached, which says whether the graph, dependency graph, ontology, centrality and entity RAG chunks are already built; allowed_roots; output_dir; providers_configured (from the cloudg config); next_steps only when nothing is loaded.
Allowed roots come from Workspace(allowed_roots=...), else $CLOUDG_MCP_ALLOWED_ROOTS (os.pathsep-separated), else the current directory plus the configured report directory, leaving out either one when it is / or the home directory (with neither left, the workspace refuses to start). The output directory is added to the roots if it is outside them.
Related: load_dataset, list_datasets, dataset_summary.
list_datasets¶
Category workspace · sensitivity internal · capabilities read_state · readOnly true, destructive false, idempotent true, openWorld false · outputSchema no · timeout: layer default (300 s)
The dataset listing without the directory information, with an active flag on each entry.
Arguments: none.
Called with the arguments below, list_datasets returned this structuredContent.
{
"active_dataset": "sample",
"datasets": [
{
"name": "sample",
"kind": "inventory",
"source": "<estate>/inventory",
"loaded_at": "2026-10-09T14:25:12+00:00",
"version": 0,
"providers": [
"aws",
"... 2 more"
],
"assets": 44,
"edges": 56,
"findings": 12,
"compliance_results": 8,
"cached": {
"graph": false,
"dependency_graph": false,
"ontology": false,
"centrality": false,
"rag_entity": false
},
"active": true
},
"... 1 more"
],
"total": 2
}
Related: select_dataset, unload_dataset.
load_dataset¶
Category workspace · sensitivity internal · capabilities read_fs, read_state, write_state · readOnly false, destructive false, idempotent false, openWorld false · outputSchema no · timeout: layer default (300 s)
Loads a file or directory as a dataset and makes it active unless activate is false. Returns loaded (the name), active, and every field of dataset_summary, and links the cloudg://datasets/{name}/summary resource.
| Argument | Type | Default | Constraints | Description |
|---|---|---|---|---|
path |
string | required | min length 1 | File or directory inside an allowed root: inventory-map.json (or its directory), a cloudg findings.json report, a generic {assets, edges, findings} JSON, or native Prowler / ScoutSuite / Checkov / Trivy output. |
name |
string | "" |
Dataset name; default derives from the path. | |
kind |
string | "auto" |
one of auto, inventory, report, generic, prowler, scoutsuite, checkov, trivy |
Content type; 'auto' detects it. |
activate |
boolean | true |
Make it the active dataset. | |
replace |
boolean | false |
Overwrite an existing dataset with the same name (otherwise a name clash is an error). |
path is resolved against the first allowed root when relative, symlinks included, and must exist inside an allowed root. Without name the dataset is named after the parent directory for an inventory-map.json file and after the file or directory stem otherwise, with -2, -3... added when the name is taken. An explicit name that is already loaded is refused before the file is read, unless replace is true. With kind: "auto" the content is detected as follows:
| Path | Detected kind |
|---|---|
Directory with inventory-map.json |
inventory |
Directory with findings.json |
report |
Directory containing scoutsuite_results*.js |
scoutsuite |
Directory containing results_json.json |
checkov |
.js file or file named scoutsuite_results* |
scoutsuite |
JSON object with findings and metadata or summary |
report |
JSON object with assets and edges and unresolved_references or regions |
inventory |
Other JSON object with assets, edges or findings |
generic |
JSON object with Results and ArtifactName, SchemaVersion or ArtifactType |
trivy |
JSON object with check_type, or a results object |
checkov |
JSON object with Findings |
prowler |
JSON array whose first item has check_type |
checkov |
JSON array whose first item has ProductArn or SchemaVersion |
prowler |
| JSON array whose first record looks like OCSF (Prowler 4's default output) | prowler |
JSON array whose first item has severity, title or resource_id |
generic |
| Other JSON Lines | prowler (ASFF or OCSF records) |
An inventory load also reads findings.json from the same directory when present (its path lands in the dataset metadata as findings_source). Scanner output is parsed with the cloudg ingest parsers and normalised. The Prowler parser reads ASFF and OCSF (the default since Prowler 4), record by record, and Prowler's passing checks give PASS results for the ruleset controls only they cover. A Prowler output directory is not detected; pass kind: "prowler" for it.
Loading the sample inventory directory:
Called with the arguments below, load_dataset returned this structuredContent.
{
"loaded": "sample",
"active": true,
"dataset": "sample",
"kind": "inventory",
"source": "<estate>/inventory",
"loaded_at": "2026-10-09T14:25:12+00:00",
"version": 0,
"providers": [
"aws",
"azure",
"gcp"
],
"total_assets": 44,
"total_edges": 56,
"total_findings": 12,
"open_findings": 11,
"suppressed_findings": 1,
"severity_breakdown": {
"CRITICAL": 2,
"HIGH": 5,
"MEDIUM": 2,
"LOW": 1,
"INFO": 1
},
"accounts": 5,
"regions": 5,
"internet_exposed": 6,
"cross_account_edges": 3,
"unlinked_assets": 2,
"unresolved_references": 1,
"compliance_frameworks": [
"CIS-AWS",
"CIS-Azure",
"CIS-GCP",
"... 2 more"
],
"assets_by_type": {
"SECURITY_GROUP": 4,
"IAM_ROLE": 4,
"CLOUD_ACCOUNT": 3,
"EC2": 3,
"S3_BUCKET": 3,
"...": "10 more"
},
"assets_by_provider": {
"AWS": 36,
"AZURE": 5,
"GCP": 3
},
"edges_by_type": {
"CONTAINS": 12,
"GOVERNS": 1,
"SECURITY_GROUP_RULE": 4,
"INTERNET_EXPOSED": 4,
"ATTACHED_TO": 6,
"...": "10 more"
},
"has_organization": true,
"coverage_records": 1,
"organization": {
"id": "o-sample",
"accounts": 2,
"ous": 1,
"control_tower": true,
"governed_regions": [
"us-east-1",
"eu-west-1"
]
}
}
The result also carried these resource links in content:
[
{
"type": "resource_link",
"uri": "cloudg://datasets/sample/summary",
"name": "sample summary",
"title": "Dataset summary",
"mimeType": "application/json"
}
]
Loading the report file as a second, inactive dataset by a relative path:
Called with the arguments below, load_dataset returned this structuredContent.
{
"loaded": "report",
"active": false,
"dataset": "report",
"kind": "report",
"source": "<estate>/report/findings.json",
"loaded_at": "2026-10-09T14:25:12+00:00",
"version": 0,
"providers": [
"aws",
"azure",
"... 1 more"
],
"total_assets": 44,
"total_edges": 56,
"total_findings": 12,
"open_findings": 11,
"suppressed_findings": 1,
"severity_breakdown": {
"CRITICAL": 2,
"HIGH": 5,
"MEDIUM": 2,
"LOW": 1,
"INFO": 1
},
"accounts": 5,
"regions": 5,
"internet_exposed": 6,
"cross_account_edges": 3,
"unlinked_assets": 2,
"unresolved_references": 0,
"compliance_frameworks": [
"CIS-AWS",
"CIS-Azure",
"... 2 more"
],
"assets_by_type": {
"SECURITY_GROUP": 4,
"IAM_ROLE": 4,
"CLOUD_ACCOUNT": 3,
"EC2": 3,
"S3_BUCKET": 3,
"...": "10 more"
},
"assets_by_provider": {
"AWS": 36,
"AZURE": 5,
"GCP": 3
},
"edges_by_type": {
"CONTAINS": 12,
"IAM_TRUST": 2,
"GOVERNS": 1,
"SECURITY_GROUP_RULE": 4,
"ATTACHED_TO": 6,
"...": "10 more"
},
"has_organization": false,
"coverage_records": 0
}
Errors:
{
"tool": "load_dataset",
"arguments": {
"path": "/etc/passwd"
},
"isError": true,
"text": "Path /etc/passwd is outside the allowed roots (<estate>). Ask the operator to add its directory to the workspace's allowed roots.",
"_meta": {
"cloudg/error_code": -31001
}
}
{
"tool": "load_dataset",
"arguments": {
"path": "nope.json"
},
"isError": true,
"text": "Path does not exist: nope.json",
"_meta": {
"cloudg/error_code": -32602
}
}
{
"tool": "load_dataset",
"arguments": {
"path": "report/findings.json",
"name": "sample"
},
"isError": true,
"text": "A dataset with that name already exists. Choose another name (the error data suggests a free one), or pass replace=true to overwrite it.",
"_meta": {
"cloudg/error_code": -32602,
"cloudg/error_data": {
"existing": "sample",
"suggested": "sample-2"
}
}
}
Prowler OCSF output, one failing record for the root account's hardware MFA, as a third, inactive dataset:
Called with the arguments below, load_dataset returned this structuredContent.
{
"loaded": "ocsf",
"active": false,
"dataset": "ocsf",
"kind": "prowler",
"source": "<estate>/scans/prowler-ocsf.json",
"loaded_at": "2026-10-10T06:24:43+00:00",
"version": 0,
"providers": [],
"total_assets": 0,
"total_edges": 0,
"total_findings": 1,
"open_findings": 1,
"suppressed_findings": 0,
"severity_breakdown": {
"HIGH": 1
},
"accounts": 0,
"regions": 0,
"internet_exposed": 0,
"cross_account_edges": 0,
"unlinked_assets": 0,
"unresolved_references": 0,
"compliance_frameworks": [
"AWS-Foundational-Security-Best-Practices-AWS",
"CIS-AWS",
"GDPR-AWS",
"... 4 more"
],
"assets_by_type": {},
"assets_by_provider": {},
"edges_by_type": {},
"has_organization": false,
"coverage_records": 0
}
Related: workspace_status, ingest_reports (add findings to an existing dataset), map_inventory (collect instead of load).
select_dataset¶
Category workspace · sensitivity internal · capabilities read_state, write_state · readOnly false, destructive false, idempotent true, openWorld false · outputSchema no · timeout: layer default (300 s)
Makes a loaded dataset the default for every tool, resource and prompt. Returns active_dataset plus the dataset's listing entry.
| Argument | Type | Default | Constraints | Description |
|---|---|---|---|---|
name |
string | required | min length 1 | Dataset to activate. |
Called with the arguments below, select_dataset returned this structuredContent.
{
"active_dataset": "report",
"name": "report",
"kind": "report",
"source": "<estate>/report/findings.json",
"loaded_at": "2026-10-09T14:25:12+00:00",
"version": 0,
"providers": [
"aws",
"azure",
"gcp"
],
"assets": 44,
"edges": 56,
"findings": 12,
"compliance_results": 8,
"cached": {
"graph": false,
"dependency_graph": false,
"ontology": false,
"centrality": false,
"rag_entity": false
}
}
An unknown name fails, with the loaded datasets in error_data (see The dataset argument).
unload_dataset¶
Category workspace · sensitivity internal · capabilities read_state, write_state · readOnly false, destructive true, idempotent true, openWorld false · outputSchema no · timeout: layer default (300 s)
Removes a dataset from memory. Files on disk are untouched; suppressions and ingested findings held only in memory are lost. When the active dataset is removed, the most recently added remaining dataset becomes active (or none). Returns unloaded, the new active_dataset and remaining names.
| Argument | Type | Default | Constraints | Description |
|---|---|---|---|---|
name |
string | required | min length 1 | Dataset to drop. |
Called with the arguments below, unload_dataset returned this structuredContent.
{
"unloaded": "pre-ingest",
"active_dataset": "after",
"remaining": [
"sample",
"report",
"after"
]
}
Related: snapshot_dataset (keep a copy first), export_report (persist to disk).
snapshot_dataset¶
Category workspace · sensitivity internal · capabilities read_state, write_state · readOnly false, destructive false, idempotent false, openWorld false · outputSchema no · timeout: layer default (300 s)
Deep copies a dataset (assets, edges, findings, compliance results, coverage, organization, suppression reasons) under new_name, with kind: "snapshot" and source: "snapshot of <name>". The copy is not activated. Take one before ingesting findings or re-collecting, then compare with diff_datasets. Returns snapshot plus the new dataset's listing entry.
| Argument | Type | Default | Constraints | Description |
|---|---|---|---|---|
new_name |
string | required | min length 1 | Name for the copy. |
dataset |
string | "" |
Dataset name (see list_datasets). Empty = the active dataset. | |
replace |
boolean | false |
Overwrite an existing dataset named new_name (otherwise a name clash is an error). |
Called with the arguments below, snapshot_dataset returned this structuredContent.
{
"snapshot": "baseline",
"name": "baseline",
"kind": "snapshot",
"source": "snapshot of sample",
"loaded_at": "2026-10-09T14:25:12+00:00",
"version": 0,
"providers": [
"aws",
"azure",
"gcp"
],
"assets": 44,
"edges": 56,
"findings": 12,
"compliance_results": 8,
"cached": {
"graph": false,
"dependency_graph": false,
"ontology": false,
"centrality": false,
"rag_entity": false
}
}
A new_name that is already loaded is refused unless replace is true:
{
"tool": "snapshot_dataset",
"arguments": {
"new_name": "report",
"dataset": "ocsf-test"
},
"isError": true,
"text": "A dataset with that name already exists. Choose another name (the error data suggests a free one), or pass replace=true to overwrite it.",
"_meta": {
"cloudg/error_code": -32602,
"cloudg/error_data": {
"existing": "report",
"suggested": "report-2"
}
}
}
diff_datasets¶
Category workspace · sensitivity confidential · capabilities read_state · readOnly true, destructive false, idempotent true, openWorld false · outputSchema yes · timeout: layer default (300 s)
Compares two datasets. Assets are matched on ARN (else id), so the same resource matches across collections even if internal ids differ. An asset counts as changed when its name, type, region, account, tags, exposure flag or metadata (hashed) differ. Edges are matched on (source key, target key, edge type, relationship) where keys are ARNs or ids. Findings are matched on (source tool, scanner check id or title, resource key).
| Argument | Type | Default | Constraints | Description |
|---|---|---|---|---|
base |
string | required | min length 1 | Baseline dataset (before). |
target |
string | "" |
Dataset to compare (after); empty = active. | |
limit |
integer | 50 |
1..500 | Max items per section. |
Every section is {count, items, truncated} with at most limit items:
| Field | Content |
|---|---|
base, target |
Dataset names compared. |
assets.added, assets.removed |
Briefs with id, key (ARN or id), name, type, account_id, region. |
assets.changed |
Same brief plus changed_fields, and internet_exposed: {before, after} when exposure changed. |
assets.unchanged |
Count only. |
edges.added, edges.removed |
source, target (as keys), edge_type, relationship. |
findings.new, findings.resolved |
id, title, severity, resource, source_tool. |
findings.severity_changed |
The same plus severity_before. |
newly_internet_exposed |
Changed assets that became exposed plus added assets that are exposed. |
The example compares baseline (a snapshot of the sample) with after, a copy of the sample inventory directory edited by the capture script: the WAF and its PROTECTS edge removed, web-2 made internet-exposed with a new INTERNET_EXPOSED edge and a changed owner tag, the SSH finding removed and the RDS backup finding raised from MEDIUM to HIGH.
Called with the arguments below, diff_datasets returned this structuredContent.
{
"base": "baseline",
"target": "after",
"assets": {
"added": {
"count": 0,
"items": [],
"truncated": false
},
"removed": {
"count": 1,
"items": [
{
"id": "waf-web",
"key": "arn:aws:wafv2:us-east-1:111111111111:regional/webacl/web-acl/1",
"name": "web-acl",
"type": "WAF_WEB_ACL",
"account_id": "111111111111",
"region": "us-east-1"
}
],
"truncated": false
},
"changed": {
"count": 1,
"items": [
{
"id": "web-2",
"key": "arn:aws:ec2:us-east-1:111111111111:instance/i-0web2",
"name": "web-2",
"type": "EC2",
"account_id": "111111111111",
"region": "us-east-1",
"changed_fields": [
"internet_exposed",
"tags"
],
"internet_exposed": {
"before": false,
"after": true
}
}
],
"truncated": false
},
"unchanged": 42
},
"edges": {
"added": {
"count": 1,
"items": [
{
"source": "0.0.0.0/0",
"target": "arn:aws:ec2:us-east-1:111111111111:instance/i-0web2",
"edge_type": "INTERNET_EXPOSED",
"relationship": "INTERNET_REACHABLE"
}
],
"truncated": false
},
"removed": {
"count": 1,
"items": [
{
"source": "arn:aws:wafv2:us-east-1:111111111111:regional/webacl/web-acl/1",
"target": "arn:aws:elasticloadbalancing:us-east-1:111111111111:loadbalancer/app/web-alb/abc",
"edge_type": "PROTECTS",
"relationship": null
}
],
"truncated": false
}
},
"findings": {
"new": {
"count": 0,
"items": [],
"truncated": false
},
"resolved": {
"count": 1,
"items": [
{
"id": "f-ssh-open",
"title": "Security group allows SSH (22) from 0.0.0.0/0",
"severity": "CRITICAL",
"resource": "arn:aws:ec2:us-east-1:111111111111:security-group/sg-0admin",
"source_tool": "prowler"
}
],
"truncated": false
},
"severity_changed": {
"count": 1,
"items": [
{
"id": "f-db-backup",
"title": "RDS backup retention below 7 days",
"severity": "HIGH",
"resource": "arn:aws:rds:us-east-1:111111111111:db:orders-db",
"source_tool": "prowler",
"severity_before": "MEDIUM"
}
],
"truncated": false
}
},
"newly_internet_exposed": {
"count": 1,
"items": [
{
"id": "web-2",
"key": "arn:aws:ec2:us-east-1:111111111111:instance/i-0web2",
"name": "web-2",
"type": "EC2",
"account_id": "111111111111",
"region": "us-east-1",
"changed_fields": [
"internet_exposed",
"tags"
],
"internet_exposed": {
"before": false,
"after": true
}
}
],
"truncated": false
}
}
{
"tool": "diff_datasets",
"arguments": {
"base": "yesterday"
},
"isError": true,
"text": "No such dataset. 5 dataset(s) are loaded: see datasets in the error data, or call list_datasets.",
"_meta": {
"cloudg/error_code": -32602,
"cloudg/error_data": {
"value": "yesterday",
"datasets": [
{
"dataset": "sample"
},
{
"dataset": "report"
},
{
"dataset": "after"
},
"... 2 more"
]
}
}
}
Related: snapshot_dataset, the drift_review prompt, Recipe: drift between two snapshots.
dataset_summary¶
Category workspace · sensitivity internal · capabilities read_state · readOnly true, destructive false, idempotent true, openWorld false · outputSchema yes · timeout: layer default (300 s)
Headline numbers for one dataset. Links cloudg://datasets/{name}/summary, which returns the same object.
| Argument | Type | Default | Constraints | Description |
|---|---|---|---|---|
dataset |
string | "" |
Dataset name (see list_datasets). Empty = the active dataset. |
Called with the arguments below, dataset_summary returned this structuredContent.
{
"dataset": "sample",
"kind": "inventory",
"source": "<estate>/inventory",
"loaded_at": "2026-10-09T14:25:12+00:00",
"version": 0,
"providers": [
"aws",
"azure",
"gcp"
],
"total_assets": 44,
"total_edges": 56,
"total_findings": 12,
"open_findings": 11,
"suppressed_findings": 1,
"severity_breakdown": {
"CRITICAL": 2,
"HIGH": 5,
"MEDIUM": 2,
"LOW": 1,
"INFO": 1
},
"accounts": 5,
"regions": 5,
"internet_exposed": 6,
"cross_account_edges": 3,
"unlinked_assets": 2,
"unresolved_references": 1,
"compliance_frameworks": [
"CIS-AWS",
"CIS-Azure",
"CIS-GCP",
"... 2 more"
],
"assets_by_type": {
"SECURITY_GROUP": 4,
"IAM_ROLE": 4,
"CLOUD_ACCOUNT": 3,
"EC2": 3,
"S3_BUCKET": 3,
"...": "10 more"
},
"assets_by_provider": {
"AWS": 36,
"AZURE": 5,
"GCP": 3
},
"edges_by_type": {
"CONTAINS": 12,
"GOVERNS": 1,
"SECURITY_GROUP_RULE": 4,
"INTERNET_EXPOSED": 4,
"ATTACHED_TO": 6,
"...": "10 more"
},
"has_organization": true,
"coverage_records": 1,
"organization": {
"id": "o-sample",
"accounts": 2,
"ous": 1,
"control_tower": true,
"governed_regions": [
"us-east-1",
"eu-west-1"
]
}
}
The result also carried these resource links in content:
[
{
"type": "resource_link",
"uri": "cloudg://datasets/sample/summary",
"name": "sample summary",
"mimeType": "application/json"
}
]
| Field | Meaning |
|---|---|
dataset, kind, source, loaded_at, version, providers |
Identity of the dataset. |
total_assets, total_edges, total_findings |
Raw counts. |
open_findings, suppressed_findings, severity_breakdown |
Open findings by severity, worst first. |
accounts |
Distinct account ids (excluding unknown). |
regions |
Distinct regions, as a count. |
internet_exposed |
Assets flagged exposed. |
cross_account_edges |
Edges whose ends are in different accounts. |
unlinked_assets |
Assets with no edge at all, excluding organization, OU and account assets. |
unresolved_references |
Count of linker misses (unresolved_references tool). |
compliance_frameworks |
Frameworks present in the compliance results. |
assets_by_type |
Top 15 types. |
assets_by_provider, edges_by_type |
Full breakdowns. |
has_organization, organization |
Organization summary when discovery data exists. |
coverage_records |
Number of collection coverage records (from live collections, and from inventory maps saved with them). |