{
  "markdown": "<!-- mcp-name: io.github.JohannsenLum/is-it-ai-mcp -->\n\n# is-it-ai-mcp\n\n<h2 align=\"center\">Read the file's own claim about itself, and check whether that claim still holds.</h2>\n\n<p align=\"center\">\n  An image can carry a signed Content Credential declaring how it was made.<br>\n  This server reads it, checks the signature against the bytes, and hands both<br>\n  back to your AI assistant, which cannot see either on its own.\n</p>\n\nAn MCP server that reads the C2PA Content Credential embedded in an image and reports\nwhat the file declares about its own origin, plus whether that declaration still\nverifies against the bytes in front of you.\n\n**📖 Documentation: [mcp.johannsenlum.com/is-it-ai](https://mcp.johannsenlum.com/is-it-ai)**\n· [install guide](https://mcp.johannsenlum.com/is-it-ai/install) ·\n[tool reference](https://mcp.johannsenlum.com/is-it-ai/tools)\n\n**🔍 Try it without installing anything: [ai.johannsenlum.com/is-it-ai](https://ai.johannsenlum.com/is-it-ai)**\nruns the same checks in your browser, on your machine, with nothing uploaded.\n\n[![PyPI](https://img.shields.io/pypi/v/is-it-ai-mcp)](https://pypi.org/project/is-it-ai-mcp/)\n[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/JohannsenLum/is-it-ai-mcp/blob/main/LICENSE)\n\n## What it does\n\nIt opens an image, looks for an embedded C2PA manifest (a Content Credential), and if\none exists, reports two separate things: what the manifest claims about how the image\nwas made, and whether the cryptographic signature over that manifest still matches the\nfile's current bytes. It does not look at pixels and it does not guess. If there is no\nmanifest, or the manifest exists but the signature is broken, it says so plainly instead\nof inventing an answer.\n\nSee [docs/pass-fail.md](docs/pass-fail.md) for why \"the signature verified\" and \"the\nclaim is true\" are two different questions, and why this server only ever answers the\nfirst one.\n\n## Install\n\n### Running the server\n\n`is-it-ai-mcp` is published on PyPI. Run it with:\n\n```bash\nuvx is-it-ai-mcp\n```\n\n**Install from source (contributors / unreleased `main`).** Not part of the normal\ninstall path above, only needed if you want the latest unreleased code instead of the\npublished PyPI release:\n\n```bash\nuvx --from git+https://github.com/JohannsenLum/is-it-ai-mcp is-it-ai-mcp\n```\n\nOr run from a local clone:\n\n```bash\ngit clone https://github.com/JohannsenLum/is-it-ai-mcp\ncd is-it-ai-mcp\nuv sync\n```\n\nNo account, API key, or environment variable is required. The server only reads local\nfiles you point it at.\n\n### Claude Code and Claude Desktop\n\n<a id=\"config-claude-code\"></a>\n<details>\n<summary><strong>Claude Code</strong>: <code>~/.claude.json</code></summary>\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"is-it-ai\": {\n      \"command\": \"uvx\",\n      \"args\": [\"is-it-ai-mcp\"]\n    }\n  }\n}\n```\n\n</details>\n\n<a id=\"config-claude-desktop\"></a>\n<details>\n<summary><strong>Claude Desktop</strong>: <code>claude_desktop_config.json</code></summary>\n\nmacOS: `~/Library/Application Support/Claude/claude_desktop_config.json`\nWindows: `%APPDATA%\\Claude\\claude_desktop_config.json`\n\nCopy this JSON in via **Settings → Developer → Edit Config**:\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"is-it-ai\": {\n      \"command\": \"uvx\",\n      \"args\": [\"is-it-ai-mcp\"]\n    }\n  }\n}\n```\n</details>\n\n## Tools\n\n### `check_image_provenance`\n\nGive it the path to one image file on disk. It returns:\n\n- **`verdict`**: one of the three states below (`AI_DECLARED`, `NO_AI_DECLARED`,\n  `UNKNOWN`).\n- **`reason`**: the specific basis for that verdict, for example `no-credential`,\n  `credential-invalid`, `ai-source-type`, `capture-source-type`, or\n  `credential-silent`.\n- **`what_this_does_not_mean`**: a plain-language caveat attached to every verdict, so\n  the result cannot be quietly upgraded into a stronger claim than the evidence\n  supports.\n- **`signature`**: the raw `validation_state`, a `verified` boolean, and any\n  `failure_codes` the signature check produced.\n- **`declared`**: what the manifest claims when one exists. `signer`, `claim_generator`,\n  and `signed_at` are free text pulled straight from the file, so each is guarded per\n  the Security section below before it reaches you. `source_types` (IPTC\n  digital-source-type URIs, drawn from a closed vocabulary) and `watermark_declared` (a\n  boolean) cannot carry attacker-authored text, so both are returned as-is and are\n  never fenced.\n\nA file that cannot be read (bad path, unsupported format) comes back as a distinct\nerror result rather than a fake `UNKNOWN`. It needs a real path on disk, not the image\nbytes themselves. See [Note on file paths](#note-on-file-paths).\n\n### `scan_directory`\n\nGive it a folder. It walks the tree, runs every image it finds through the same check,\nand returns aggregate counts by verdict and by signer, plus a capped sample of the\nfiles that carried a credential, rather than a line per file. This is the tool that\nreproduces this project's own 401-image sweep (see below) against any directory you\npoint it at.\n\n## The three verdicts\n\n| Verdict | Means | Reachable only when |\n|---|---|---|\n| `AI_DECLARED` | The file's manifest asserts AI or algorithmic generation | A manifest is present, its signature verifies, and it carries an IPTC digital-source-type marking AI involvement |\n| `NO_AI_DECLARED` | The file's manifest asserts a real-world capture | A manifest is present, its signature verifies, and it carries an IPTC digital-source-type marking a capture (camera, film, print, minor edits) |\n| `UNKNOWN` | Nothing usable was established | Everything else, including no manifest, a broken signature, or a manifest that never recorded a source type |\n\n**`UNKNOWN` is the default and by far the most common answer.** In a scan of 401 real\nimages, only 14 carried a Content Credential at all, and every one of those was from\nOpenAI: about 96.5% had no credential whatsoever. Expect `UNKNOWN` on almost everything\nyou check. That is not a bug in this server, it is the current state of image\nprovenance in the wild.\n\n## What this cannot tell you\n\n- **It does not detect AI from pixels.** There is no image analysis here at all, no\n  model looking at the picture. Every verdict comes from a cryptographically signed\n  text record, or from the absence of one.\n- **A missing credential is not evidence of anything.** An unmarked AI image and an\n  unmarked photograph are indistinguishable to this tool. Screenshotting, re-saving, or\n  passing an image through almost any editor strips the credential, AI-made or not.\n  `UNKNOWN` means \"no usable record survived,\" never \"no AI was involved.\"\n- **A valid signature does not make the signer's claims true.** The signature proves\n  the manifest has not been altered since it was signed and confirms who signed it. It\n  proves nothing about whether the signer told the truth. Anyone can generate their own\n  certificate, sign their own file, and assert whatever origin they like: it will\n  verify perfectly and still be false. Verification is about the seal, not the letter.\n  See [docs/pass-fail.md](docs/pass-fail.md).\n\n## Why it exists\n\nWhen you attach an image to a model, the image is decoded to pixels and re-encoded\nbefore the model ever sees it. Container metadata, including any C2PA manifest, never\nreaches the model: the model sees an RGB array, nothing more. Without a tool, it cannot\nread a manifest at all, and it will tend to guess whether an image is AI-generated from\nvisual artefacts instead. Published benchmarks put that guess at roughly 18 to 31%\naccuracy, little better than chance. This server exists so the model reads the actual\nsigned record on disk instead of pattern-matching on JPEG compression artefacts.\n\n## Security\n\nEvery human-readable string returned by this server originated inside the file being\ninspected: signer name, claim-generator string, and any free-text reason or label. That\ntext was written by whoever produced the file, not by you, and it lands in the same\ncontext window as your own instructions. A field labelled \"signer\" carries an air of\nverified authority, but the signature covers the asset's bytes, not the honesty of any\nstring inside the manifest. Anyone can sign their own file with their own certificate\nand write anything they like in it.\n\nBefore any such string is returned, it is cleaned of control characters, length-capped,\nand wrapped in a fenced boundary that marks it explicitly as untrusted data rather than\ninstructions, with a random nonce the file's author could not have pre-guessed.\nStructural values, the verdict itself, the validation state, and the IPTC source-type\nURIs are drawn from closed vocabularies this server controls and are never fenced,\nsince they cannot carry attacker-authored text.\n\nSee [`src/is_it_ai_mcp/safety.py`](src/is_it_ai_mcp/safety.py) for the implementation\nand the full threat model in its module docstring.\n\n## Note on file paths\n\nThis tool takes a **file path**, not image bytes. In Claude Code, an image you paste\ninto the conversation is written to disk first, so its path is reachable and this just\nworks. In other clients, a pasted or attached image may exist only as inline data with\nno path on the filesystem the server can reach, in which case there is nothing for\nthis tool to open. If a check comes back unable to find the file, save the image to\ndisk first and point the tool at that path.\n\n## Development\n\n```bash\nuv sync --extra dev\nuv run pytest -v\n```\n\n`--extra dev` matters: without it, `pytest-asyncio` (needed for `asyncio_mode = \"auto\"`\nin `pyproject.toml`) is not installed, and pytest prints an \"Unknown config option:\nasyncio_mode\" warning on every run.\n\nTests run against the fixtures in [`fixtures/`](fixtures/), a real signed image plus\ntampered and stripped variants with hand-verified expected outcomes. See\n[`fixtures/GROUND-TRUTH.md`](fixtures/GROUND-TRUTH.md) for how each was produced and\nwhat each is expected to prove.\n\n## Licence\n\n[MIT](LICENSE) © 2026 Johannsen Lum.\n\nUse it, change it, redistribute it, build something commercial on it: the only\ncondition is that you keep the copyright notice and licence text. It comes with no\nwarranty of any kind.\n\nContributions are accepted under the same licence.\n",
  "bytes": 10318,
  "sha": "6117fa62c0a547f341b9cd57d583f271de2e28e0036ee033e8c6dcb3ae6ec855",
  "repo_slug": "johannsenlum/is-it-ai-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_johannsenlum_is_it_ai_mcp_fe13e6a5/readme"
}