TrustMCPdocs

Continuous OSCAL

A point-in-time export answers "what is true now". That is enough for an annual review and useless for continuous monitoring, because you have no way to know when to ask again short of re-pulling every document on a timer and diffing megabytes of JSON.

TrustMCP closes that gap with three mechanisms over one per-vendor change log.

The change log

Every change to a vendor's published evidence appends an entry. The entry's sequence number is your cursor: monotonic and gapless within a vendor, so since=12 is unambiguous and you can tell "nothing changed" from "I missed something" without comparing timestamps.

Crucially, each change names the OSCAL models it invalidates — so a branding edit never makes you re-pull a POA&M.

EventInvalidates
artifact.created / artifact.updated / artifact.version / artifact.deletedcomponent-definition, SSP, assessment-results, POA&M
claims.replacedcomponent-definition, SSP, assessment-results, POA&M
controls.replacedassessment-results, POA&M
subprocessors.replacedcomponent-definition, SSP, assessment-results
mark.changedcomponent-definition, assessment-results, POA&M
drive.syncedevery evidence-bearing model
oscal.importedevery evidence-bearing model
publishedeverything

Poll

The simplest mechanism, and the only one that survives your being offline for a week.

GET /v1/vendors/{vid}/oscal/changes?since=42&limit=100     (scope: attestations)
{
  "vendor_id": "vnd_acme",
  "since": 42,
  "cursor": 47,
  "latest": 47,
  "has_more": false,
  "changes": [
    {
      "sequence": 43,
      "event": "artifact.version",
      "subject": "art_soc2",
      "models": ["component-definition", "system-security-plan", "assessment-results",
                 "plan-of-action-and-milestones"],
      "detail": { "version": 3, "sha256": "…" },
      "at": "2026-08-19T09:14:22Z"
    }
  ]
}

Save cursor and pass it as since next time. has_more means the page was full — keep paging before treating yourself as caught up. Narrow to what you care about with &models=plan-of-action-and-milestones.

Stream

For consumers that want push without running a webhook endpoint:

GET /v1/vendors/{vid}/oscal/stream?since=42
Accept: text/event-stream
event: oscal.change
id: 43
data: {"vendor_id":"vnd_acme","cursor":43,"change":{…},"pull":{"assessment-results":"https://…"}}

Last-Event-ID is honored on reconnect, so a dropped connection resumes from the last change you actually received rather than replaying from zero or skipping the gap. The stream closes itself after a bounded run and emits oscal.reconnect with your cursor; reconnect with it.

Subscribe

A registered webhook, delivered as changes are recorded:

POST /v1/vendors/{vid}/oscal/subscriptions      (scope: attestations)
{
  "url": "https://grc.globex.com/hooks/trustmcp",
  "secret": "shared-secret",
  "models": ["plan-of-action-and-milestones"]
}

Deliveries are HMAC-SHA256 signed with your secret in X-TrustMCP-Signature, the same scheme as vendor access webhooks. The payload carries the change and the URLs to re-pull:

{
  "event": "oscal.change",
  "data": {
    "vendor_id": "vnd_acme",
    "cursor": 43,
    "change": { "event": "artifact.version", "models": ["…"] },
    "pull": { "plan-of-action-and-milestones": "https://network.trustmcp.app/v1/…" }
  }
}

The subscription is bound to the access key that created it: revoking or expiring the key stops delivery, so entitlement and notification never drift apart. models is a filter — omit it for everything. An endpoint that fails repeatedly is suspended rather than retried forever; a dead receiver should not slow every future evidence update.

The loop, in practice

cursor = load_cursor()
batch = client.get_oscal_changes(vendor_id, cursor)
invalidated = {m for c in batch["changes"] for m in c["models"]}

for model in invalidated:
    document = client.get_oscal_model(vendor_id, model)
    ingest(document)

save_cursor(batch["cursor"])

Through MCP, that whole loop is one tool call:

poll_oscal_for_changes(vendor_id, since=cursor)

It returns the new cursor, the changes, and only the documents that actually moved — nothing at all when nothing changed.

Digests as a second line of defense

GET /v1/vendors/{vid}/oscal/bundle returns a content digest per document alongside the current cursor. Because exports are deterministic, an unchanged document hashes identically forever. Compare digests to confirm a re-pull actually differs before you spend a re-parse — useful when reconciling after downtime, or when you want a cheap integrity check independent of the change log.

What this is not

The change log records that published evidence moved, not a security event stream. A vendor uploading a new pen test and a vendor's control failing look the same here — a change to the underlying documents. The judgement stays in your tooling: pull the invalidated models, read the findings, apply your own policy.