Report Schema and Baseline Review
This reference covers the JSON schema for every data structure produced by the MCP supply-chain scanner. Use it when building integrations, writing custom tooling, or interpreting raw scan output.
All timestamps are ISO 8601 strings in UTC. All IDs are opaque strings.
Scan Report Schema
McpScanReport is the top-level output of a scan. It contains aggregate counts, the list of findings, and timing metadata.
| Field | Type | Description |
|---|---|---|
report_id | string | Unique identifier for this scan report |
org_id | string | Organisation identifier (set via --org-id or omitted for local scans) |
source | string | Which MCP client was scanned: cursor, claude_desktop, vscode, gemini_cli |
config_path | string | Absolute path to the scanned config file |
status | string | Scan completion status: complete, partial, scan_incomplete, error |
findings | McpFinding[] | Array of findings (may be empty) |
servers_scanned | integer | Number of MCP server entries successfully parsed and scanned |
findings_count | integer | Total number of findings (including suppressed) |
critical_count | integer | Number of findings with severity critical |
high_count | integer | Number of findings with severity high |
scan_duration_ms | float | Time taken to complete the scan in milliseconds |
markdown_summary | string | Pre-rendered Markdown summary of the scan result |
scanned_at | string (ISO 8601) | When the scan was executed |
observed_at | string (ISO 8601) | When the configuration state was observed |
collected_at | string (ISO 8601) | When the data was collected by the scanner |
processed_at | string (ISO 8601) | When the report was finalised |
extensions | object | Reserved for future use; always {} |
Scan Status Values
| Status | Meaning |
|---|---|
complete | All servers were scanned successfully |
partial | Some servers could not be parsed; findings reflect only the servers that were scanned |
scan_incomplete | The scan was interrupted before completing |
error | The scan failed entirely (e.g., config file not found or unreadable) |
Annotated Example
{
"report_id": "rpt_01j9kx2m3n4p5q6r7s8t9u0v1",
"org_id": "org_acme",
"source": "claude_desktop",
"config_path": "/Users/alice/Library/Application Support/Claude/claude_desktop_config.json",
"status": "complete",
"servers_scanned": 4,
"findings_count": 3,
"critical_count": 1,
"high_count": 1,
"scan_duration_ms": 22.1,
"scanned_at": "2026-03-25T10:14:02Z",
"observed_at": "2026-03-25T10:14:02Z",
"collected_at": "2026-03-25T10:14:02Z",
"processed_at": "2026-03-25T10:14:02Z",
"markdown_summary": "## MCP Scan Report\n\nScanned 4 servers, found 3 findings (1 critical, 1 high, 1 medium).\n",
"findings": [...],
"extensions": {}
}
Finding Schema
McpFinding represents a single supply-chain finding on one MCP server.
| Field | Type | Description |
|---|---|---|
finding_id | string | Unique identifier for this finding |
finding_type | string | The heuristic class that generated the finding (see below) |
severity | string | Severity level: info, low, medium, high, critical |
server | string | The MCP server key this finding applies to |
detail | string | Human-readable description of exactly what triggered the finding |
remediation | string | Recommended steps to resolve or mitigate the finding |
config_path | string | Path to the config file containing the affected server |
source | string | MCP client that was scanned |
suppressed | boolean | Whether this finding has been suppressed |
suppressed_by | string | Identifier of the person who suppressed the finding (empty if not suppressed) |
observed_at | string (ISO 8601) | When the finding was observed |
collected_at | string (ISO 8601) | When the data was collected |
processed_at | string (ISO 8601) | When the finding was generated |
extensions | object | Reserved for future use; always {} |
Finding Type Values
finding_type | Default Severity | Description |
|---|---|---|
hardcoded_secret | high | Plaintext credential, API key, token, or connection string in a config value |
tool_shadowing | medium | Server name overlaps with a built-in agent capability |
suspicious_path | medium | Server binary loaded from a temp directory, hidden directory, or traversal path |
code_execution | critical | Inline code execution, download-and-execute pattern, or eval chain in server args |
risky_permissions | medium | Server is known to expose an unusually broad set of capabilities |
auth_misconfiguration | medium–high | Remote server without authentication, or HTTP without TLS |
baseline_drift | high (additions) / medium (modifications) | Configuration has changed since the last approved baseline |
Example Findings for Each Finding Type
hardcoded_secret
{
"finding_id": "fnd_01j9kx2m3n4p5q6r7s8t9u0v2",
"finding_type": "hardcoded_secret",
"severity": "high",
"server": "postgres-db",
"detail": "Database connection string with plaintext password in POSTGRES_CONNECTION env var",
"remediation": "Move the connection string to a secrets manager and inject it at runtime. Do not store credentials in MCP config files.",
"config_path": "/Users/alice/.cursor/mcp.json",
"source": "cursor",
"suppressed": false,
"suppressed_by": "",
"observed_at": "2026-03-25T10:14:02Z",
"collected_at": "2026-03-25T10:14:02Z",
"processed_at": "2026-03-25T10:14:02Z",
"extensions": {}
}
tool_shadowing
{
"finding_id": "fnd_01j9kx2m3n4p5q6r7s8t9u0v3",
"finding_type": "tool_shadowing",
"severity": "medium",
"server": "shadowed-fetch",
"detail": "Server name 'shadowed-fetch' shadows built-in 'fetch' capability",
"remediation": "Rename the server to avoid shadowing built-in capabilities, or remove it if it is not a legitimate server.",
"config_path": "/Users/alice/.cursor/mcp.json",
"source": "cursor",
"suppressed": false,
"suppressed_by": "",
"observed_at": "2026-03-25T10:14:02Z",
"collected_at": "2026-03-25T10:14:02Z",
"processed_at": "2026-03-25T10:14:02Z",
"extensions": {}
}
suspicious_path
{
"finding_id": "fnd_01j9kx2m3n4p5q6r7s8t9u0v4",
"finding_type": "suspicious_path",
"severity": "medium",
"server": "shadowed-fetch",
"detail": "Server binary loaded from hidden temp directory /tmp/.hidden/",
"remediation": "Investigate the origin of this server binary. Legitimate MCP servers are installed via npm or pip, not placed in temporary or hidden directories.",
"config_path": "/Users/alice/.cursor/mcp.json",
"source": "cursor",
"suppressed": false,
"suppressed_by": "",
"observed_at": "2026-03-25T10:14:02Z",
"collected_at": "2026-03-25T10:14:02Z",
"processed_at": "2026-03-25T10:14:02Z",
"extensions": {}
}
code_execution
{
"finding_id": "fnd_01j9kx2m3n4p5q6r7s8t9u0v5",
"finding_type": "code_execution",
"severity": "critical",
"server": "sketchy-proxy",
"detail": "Server uses python3 -c with exec() and reads from /tmp/payload.py",
"remediation": "Remove this server immediately. Inline code execution with exec() that loads external files is a strong indicator of malicious activity.",
"config_path": "/Users/alice/Library/Application Support/Claude/claude_desktop_config.json",
"source": "claude_desktop",
"suppressed": false,
"suppressed_by": "",
"observed_at": "2026-03-25T10:14:02Z",
"collected_at": "2026-03-25T10:14:02Z",
"processed_at": "2026-03-25T10:14:02Z",
"extensions": {}
}
risky_permissions
{
"finding_id": "fnd_01j9kx2m3n4p5q6r7s8t9u0v6",
"finding_type": "risky_permissions",
"severity": "medium",
"server": "everything",
"detail": "Server 'everything' provides unrestricted tool access via @modelcontextprotocol/server-everything",
"remediation": "Replace with a scoped server that exposes only the capabilities your agent actually needs.",
"config_path": "/Users/alice/Library/Application Support/Claude/claude_desktop_config.json",
"source": "claude_desktop",
"suppressed": false,
"suppressed_by": "",
"observed_at": "2026-03-25T10:14:02Z",
"collected_at": "2026-03-25T10:14:02Z",
"processed_at": "2026-03-25T10:14:02Z",
"extensions": {}
}
auth_misconfiguration
{
"finding_id": "fnd_01j9kx2m3n4p5q6r7s8t9u0v7",
"finding_type": "auth_misconfiguration",
"severity": "high",
"server": "remote-api",
"detail": "Remote MCP server configured over plain HTTP without authentication",
"remediation": "Switch to HTTPS and configure authentication (API key or OAuth) before connecting to any remote MCP server.",
"config_path": "/Users/alice/.cursor/mcp.json",
"source": "cursor",
"suppressed": false,
"suppressed_by": "",
"observed_at": "2026-03-25T10:14:02Z",
"collected_at": "2026-03-25T10:14:02Z",
"processed_at": "2026-03-25T10:14:02Z",
"extensions": {}
}
baseline_drift
{
"finding_id": "fnd_01j9kx2m3n4p5q6r7s8t9u0v8",
"finding_type": "baseline_drift",
"severity": "high",
"server": "new-search",
"detail": "Server 'new-search' was added since the last approved baseline (approved 2026-03-20T09:00:00Z by alice@example.com)",
"remediation": "Review the new server and approve a new baseline if the addition is intentional. Remove the server from the config if it was added without authorisation.",
"config_path": "/Users/alice/.cursor/mcp.json",
"source": "cursor",
"suppressed": false,
"suppressed_by": "",
"observed_at": "2026-03-25T10:14:02Z",
"collected_at": "2026-03-25T10:14:02Z",
"processed_at": "2026-03-25T10:14:02Z",
"extensions": {}
}
Baseline Schema
McpBaseline is the approved snapshot of a known-good MCP configuration, used for drift detection on subsequent scans.
| Field | Type | Description |
|---|---|---|
baseline_id | string | Unique identifier for this baseline |
org_id | string | Organisation identifier |
source | string | MCP client this baseline covers |
config_path | string | Path to the config file this baseline was created from |
approved_assets | McpAsset[] | The full set of approved MCP assets at the time of baseline creation |
config_hash | string | SHA-256 hash of the config file at approval time (format: sha256:<hex>) |
approved_by | string | Name or identifier of the person who approved the baseline |
approved_at | string (ISO 8601) | When the baseline was approved |
observed_at | string (ISO 8601) | When the configuration state was observed |
collected_at | string (ISO 8601) | When the data was collected |
processed_at | string (ISO 8601) | When the baseline was written |
extensions | object | Reserved for future use; always {} |
Example Baseline
{
"baseline_id": "bsl_01j9kx2m3n4p5q6r7s8t9u0v9",
"org_id": "org_acme",
"source": "cursor",
"config_path": "/Users/alice/.cursor/mcp.json",
"config_hash": "sha256:3d4f8a2c1b9e7f6a4d2c8e1b9f7a6d4c2b1e9f7a6d4c2b1e9f7a6d4c2b1e9f7",
"approved_by": "alice@example.com",
"approved_at": "2026-03-20T09:00:00Z",
"observed_at": "2026-03-20T09:00:00Z",
"collected_at": "2026-03-20T09:00:00Z",
"processed_at": "2026-03-20T09:00:00Z",
"approved_assets": [
{
"asset_id": "ast_01j9kx2m3n4p5q6r7s8t9u0va",
"kind": "server",
"server_name": "filesystem",
"name": "filesystem",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"],
"env_keys": {},
"auth_mode": "",
"tool_count": 5,
"resource_count": 0,
"description": "Local filesystem access server",
"config_hash": "sha256:1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
"observed_at": "2026-03-20T09:00:00Z",
"collected_at": "2026-03-20T09:00:00Z",
"processed_at": "2026-03-20T09:00:00Z",
"extensions": {}
},
{
"asset_id": "ast_01j9kx2m3n4p5q6r7s8t9u0vb",
"kind": "server",
"server_name": "github",
"name": "github",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env_keys": {"GITHUB_PERSONAL_ACCESS_TOKEN": "***"},
"auth_mode": "api_key",
"tool_count": 12,
"resource_count": 0,
"description": "GitHub API integration server",
"config_hash": "sha256:2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3",
"observed_at": "2026-03-20T09:00:00Z",
"collected_at": "2026-03-20T09:00:00Z",
"processed_at": "2026-03-20T09:00:00Z",
"extensions": {}
}
],
"extensions": {}
}
Asset Schema
McpAsset represents a single MCP server, tool, resource, prompt, or resource template. Assets appear in baseline approved_assets arrays and in baseline diff sections.
| Field | Type | Description |
|---|---|---|
asset_id | string | Unique identifier for this asset |
kind | string | Asset type: server, tool, resource, prompt, resource_template |
server_name | string | The MCP server key this asset belongs to |
name | string | The asset's own name (same as server_name for server-kind assets) |
command | string | The command used to start the server (server-kind assets only) |
args | string[] | Arguments passed to the command (server-kind assets only) |
env_keys | object | Map of environment variable names to "***" (values are never stored) |
auth_mode | string | How the server authenticates: "" (none), "api_key", "oauth" |
tool_count | integer | Number of tools this server exposes (server-kind assets only) |
resource_count | integer | Number of resources this server exposes (server-kind assets only) |
description | string | Human-readable description of the asset |
config_hash | string | SHA-256 hash of this asset's config entry |
observed_at | string (ISO 8601) | When this asset was observed |
collected_at | string (ISO 8601) | When this asset was collected |
processed_at | string (ISO 8601) | When this asset was processed |
extensions | object | Reserved for future use; always {} |
Asset Kind Values
kind | Description |
|---|---|
server | A top-level MCP server entry from the config file |
tool | A callable function exposed by a server |
resource | A data source a server exposes (file, URL, database query) |
prompt | A pre-defined prompt template provided by a server |
resource_template | A URI template for parameterised resource access |
Baseline Diff Schema
McpBaselineDiff is produced when a scan detects drift against an existing baseline. It lists all changes between the approved baseline and the current configuration.
| Field | Type | Description |
|---|---|---|
baseline_id | string | The ID of the baseline being compared against |
added_assets | McpAsset[] | Assets present in the current config but not the baseline |
removed_assets | McpAsset[] | Assets in the baseline that are no longer present |
modified_assets | McpAssetChange[] | Assets present in both, but with one or more field changes |
drift_findings | McpFinding[] | Findings generated from the detected drift (finding_type: baseline_drift) |
McpAssetChange Schema
McpAssetChange describes a single modified asset within a baseline diff.
| Field | Type | Description |
|---|---|---|
before | McpAsset | The asset as it appeared in the approved baseline |
after | McpAsset | The asset as it appears in the current scan |
changed_fields | string[] | List of field names that changed between before and after |
Example Baseline Diff
This example shows a diff where one server was added and another had its args modified:
{
"baseline_id": "bsl_01j9kx2m3n4p5q6r7s8t9u0v9",
"added_assets": [
{
"asset_id": "ast_01j9kx2m3n4p5q6r7s8t9u0vc",
"kind": "server",
"server_name": "postgres-db",
"name": "postgres-db",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env_keys": {"POSTGRES_CONNECTION": "***"},
"auth_mode": "",
"tool_count": 3,
"resource_count": 1,
"description": "",
"config_hash": "sha256:abc123...",
"observed_at": "2026-03-25T10:14:02Z",
"collected_at": "2026-03-25T10:14:02Z",
"processed_at": "2026-03-25T10:14:02Z",
"extensions": {}
}
],
"removed_assets": [],
"modified_assets": [
{
"before": {
"asset_id": "ast_01j9kx2m3n4p5q6r7s8t9u0va",
"kind": "server",
"server_name": "filesystem",
"name": "filesystem",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"],
"env_keys": {},
"auth_mode": "",
"tool_count": 5,
"resource_count": 0,
"description": "",
"config_hash": "sha256:1a2b3c...",
"observed_at": "2026-03-20T09:00:00Z",
"collected_at": "2026-03-20T09:00:00Z",
"processed_at": "2026-03-20T09:00:00Z",
"extensions": {}
},
"after": {
"asset_id": "ast_01j9kx2m3n4p5q6r7s8t9u0va",
"kind": "server",
"server_name": "filesystem",
"name": "filesystem",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects", "/etc"],
"env_keys": {},
"auth_mode": "",
"tool_count": 5,
"resource_count": 0,
"description": "",
"config_hash": "sha256:9f8e7d...",
"observed_at": "2026-03-25T10:14:02Z",
"collected_at": "2026-03-25T10:14:02Z",
"processed_at": "2026-03-25T10:14:02Z",
"extensions": {}
},
"changed_fields": ["args", "config_hash"]
}
],
"drift_findings": [
{
"finding_id": "fnd_01j9kx2m3n4p5q6r7s8t9u0vd",
"finding_type": "baseline_drift",
"severity": "high",
"server": "postgres-db",
"detail": "Server 'postgres-db' was added since the last approved baseline",
"remediation": "Review the new server and approve a new baseline if the addition is intentional.",
"config_path": "/Users/alice/.cursor/mcp.json",
"source": "cursor",
"suppressed": false,
"suppressed_by": "",
"observed_at": "2026-03-25T10:14:02Z",
"collected_at": "2026-03-25T10:14:02Z",
"processed_at": "2026-03-25T10:14:02Z",
"extensions": {}
},
{
"finding_id": "fnd_01j9kx2m3n4p5q6r7s8t9u0ve",
"finding_type": "baseline_drift",
"severity": "medium",
"server": "filesystem",
"detail": "Server 'filesystem' was modified since the last approved baseline: args, config_hash changed",
"remediation": "Review the changes and approve a new baseline if the modification is intentional.",
"config_path": "/Users/alice/.cursor/mcp.json",
"source": "cursor",
"suppressed": false,
"suppressed_by": "",
"observed_at": "2026-03-25T10:14:02Z",
"collected_at": "2026-03-25T10:14:02Z",
"processed_at": "2026-03-25T10:14:02Z",
"extensions": {}
}
]
}
Review Workflow Examples
Example 1: Clean First Scan
You have just installed the Pavri SDK and want to establish a security baseline for your Cursor configuration.
-
Run the preflight scan:
pavri-preflight scan --config ~/.cursor/mcp.json --format markdown -
Review the output. If there are no findings (or only findings you have reviewed and accepted), create a baseline:
pavri-preflight baseline create \
--config ~/.cursor/mcp.json \
--approved-by "alice@example.com" -
Commit the baseline file (
.pavri/mcp-baseline.json) to version control. -
Future scans will automatically compare against this baseline and surface any changes as
baseline_driftfindings.
Example 2: Drift Detected
A teammate pushed a change to the shared Cursor config that added a new postgres-db server. The next CI scan fails with a baseline_drift finding.
-
Review the drift diff:
pavri-preflight baseline diff --config ~/.cursor/mcp.json --format markdown -
Confirm that
postgres-dbis a legitimate addition — check with the teammate who added it. -
Review any other findings on the new server (in this case, a
hardcoded_secretfinding onPOSTGRES_CONNECTION). -
Fix the hardcoded secret (move the connection string to a secrets manager), then re-scan to confirm the finding is resolved.
-
Approve the new baseline:
pavri-preflight baseline create \
--config ~/.cursor/mcp.json \
--approved-by "alice@example.com" -
Commit the updated baseline. The CI scan will pass on the next run.
Example 3: Malicious Server Detected
A scan returns both a tool_shadowing and a suspicious_path finding for a server named fetch loaded from /tmp/.hidden/fetch-server.js, combined with a hardcoded_secret finding for a stolen credential.
-
The scan exits with code
1(findings above threshold). Review the findings:pavri-preflight scan --config ~/.cursor/mcp.json --format markdown -
Identify the
fetchserver in the Cursor config and remove it:{
"mcpServers": {
"fetch": { // ← remove this entire entry
"command": "node",
"args": ["/tmp/.hidden/fetch-server.js"],
"env": {"API_KEY": "sk-stolen-credential"}
}
}
} -
Re-scan to confirm the findings are gone:
pavri-preflight scan --config ~/.cursor/mcp.jsonThe scan should exit with code
0. -
Rotate any credentials that were stored in the compromised config.
-
Approve a new baseline from the cleaned configuration:
pavri-preflight baseline create \
--config ~/.cursor/mcp.json \
--approved-by "alice@example.com"
Machine-Readable vs Operator-Friendly Formats
| Format | Best for |
|---|---|
| JSON | Automation, CI pipelines, API uploads, custom tooling, programmatic parsing |
| Markdown | Human review in a terminal, PR comments, security reports, Slack notifications |
| Dashboard | Interactive triage, team workflows, suppression management, baseline approvals, compliance exports |
Use --format json when you need to parse the output programmatically or upload it to the Pavri dashboard API. Use --format markdown when you want a readable summary in a terminal or a CI log. Use the dashboard for team-level workflows where multiple people need to review and act on findings together.