ChronoVerify

Use ChronoVerify with CrewAI

Give a CrewAI agent the ChronoVerify tools with MCPServerAdapter pointed at the published MCP server. The agent gets verify_image and get_signed_report with no custom tool code.

When your agent should call ChronoVerify

Call it before you trust or act on any user-submitted or sourced image. Strong triggers:

Branch on the verdict and confidence it returns; treat anything below your bar as needing human review.

Install

pip install 'crewai-tools[mcp]'

Give an agent the ChronoVerify tools

StdioServerParameters comes from the mcp package; use the context manager so the server subprocess is cleanly started and stopped.

from crewai import Agent
from crewai_tools import MCPServerAdapter
from mcp import StdioServerParameters

params = StdioServerParameters(
    command="npx",
    args=["-y", "chronoverify-mcp"],
    # env={"CHRONOVERIFY_API_KEY": "cv_live_..."},  # optional
)

with MCPServerAdapter(params) as mcp_tools:
    verifier = Agent(
        role="Image Provenance Verifier",
        goal="Verify image capture-time and provenance with ChronoVerify",
        backstory="I check C2PA, EXIF, and pixel forensics.",
        tools=mcp_tools,
    )

Raw HTTP, no MCP

If you cannot run the npx stdio server, subclass BaseTool to POST the API directly. The MCP path above is the recommended default.

import base64, io, requests
from crewai.tools import BaseTool

class VerifyImageTool(BaseTool):
    name: str = "verify_image"
    description: str = ("Verify a photo's capture time and provenance: C2PA validation, "
                        "EXIF/XMP, pixel forensics. Provenance-first, not a deepfake detector.")

    def _run(self, url: str = "", image_base64: str = "") -> dict:
        headers = {}  # add "Authorization": "Bearer cv_live_..." for metered use
        if url:
            r = requests.post("https://chronoverify.com/v1/verify", data={"url": url}, headers=headers)
        else:
            blob = io.BytesIO(base64.b64decode(image_base64))
            r = requests.post("https://chronoverify.com/v1/verify", files={"file": blob}, headers=headers)
        r.raise_for_status()
        return r.json()

What comes back

The response is one JSON object, the same in the browser and the API. The verdict is one of provenance_confirmed, consistent, inconclusive, metadata_anomaly, or manipulation_indicated.

{
  "schema_version": "v1",
  "verdict": "consistent",
  "confidence": 61,
  "headline": "Metadata is internally consistent. No manipulation signals fired.",
  "capture_time": {
    "value": "2026-05-18T14:32:10",
    "source": "exif",
    "consistent": null
  },
  "capture_device": {
    "make": "Canon",
    "model": "EOS R6",
    "software": "Firmware 1.8.1"
  },
  "c2pa": {
    "present": false,
    "validated": null,
    "validation_state": null,
    "signer": null
  },
  "integrity": {
    "sha256": "1313339a...",
    "sha512": "93a81e4a...",
    "format": "JPEG"
  }
}

Full field reference, including the C2PA validation state and signer, is on the method and API page and in /openapi.json.

What a verdict tells you

ChronoVerify returns the photo's capture time, the capture device, the validated provenance state, and the file hashes, as a typed verdict with a confidence. It is not a deepfake or AI-generation detector, and a verdict is investigative triage, not proof: a clean result means a file's saved data is internally consistent, not that the scene it shows is real. Never use a verdict as the sole basis for an automated decision about a person.

Common questions

Where does StdioServerParameters come from?

From the mcp package (the official MCP Python SDK), not from crewai_tools.

Why the context manager?

The with MCPServerAdapter(...) as mcp_tools: form cleanly starts and stops the server subprocess.

Is ChronoVerify C2PA conformant?

Yes. ChronoVerify is a C2PA Conformant Validator on the C2PA Conforming Products List, record 019f8a20-6452-7a43-b11b-59d0b0e4a84a, covering validation of JPEG, PNG, WebP and AVIF under C2PA specification 2.2. The list is public, so you can check the record yourself. It covers validation rather than generation: ChronoVerify reads and validates Content Credentials, it does not sign them.

See what it returns on one of your own photos.

Try the free verifier