Skip to main content

Read-only MCP server that brings FirmaUY signature inspection to your AI assistant. Offline, no smart card, no signing, redacted by default.

Project description

FirmaUY MCP Inspect, AI-assisted read-only inspection for Uruguayan digital signatures

firmauy-mcp-inspect

CI PyPI version Python versions License: Apache 2.0 DeepWiki Docker

firmauy-mcp-inspect is a small, read-only MCP server that lets an AI assistant inspect documents signed with a Uruguayan cédula de identidad using FirmaUY. It verifies signatures (PDF/PAdES, XAdES XML, or a detached CMS .p7s) and validates cédula check digits. Verification runs offline and does not require a smart card.

It exists for the one task where an assistant genuinely beats the bare CLI, triaging a batch of signed documents in plain language. Point it at a folder and it verifies every file, groups the results by issuing CA, and flags anything that is not VALID or whose certificate chain is not trusted, leaving you a summary to act on.

By design it only ever inspects. It cannot sign, never sees a PIN, and redacts the signer's personal data by default, so identities stay out of the model's context.

Scope (inspection only, by design)

FirmaUY can also sign with the national ID card and read the cardholder's data. This server deliberately exposes neither of them.

  • No signing. A signature with a national ID is a legally significant act that needs the physical card and a PIN. A model must never be able to trigger it. Signing stays a human, manual action.
  • No identity or photo. fetch-identity and fetch-photo return personal and biometric data. That must not flow into a model's context, so those commands are not exposed.

What the model sees from verification is redacted by default. The model receives the indication, the trust status, and the issuer (a public CA), but not the signer's name or document number.

Why a subprocess and not the Python API

FirmaUY ships a public Python API (firmauy.api) that this server could import instead of running the CLI. That would be a mistake here, and the process boundary is deliberate:

  • The capability stays out of the process. Importing firmauy.api would load signing and cardholder-data reading into the same process as the model-driven code, one attribute away. Running the CLI means those capabilities are not present at all, which is a stronger guarantee than being present and not called.
  • The --json output is a versioned contract. It carries a schema_version and changes far more slowly than the young library API.
  • Timeouts actually work. subprocess kills a hung process. A blocking in-process call cannot be cancelled that way in Python.

The cost is a process spawn per call, which is irrelevant next to those three.

Tools

Tool What it does
verify(path, original=None, redact=True) Verify one signed file (PDF/PAdES, XAdES XML, detached CMS/.p7s). Returns the indication, per-signature trust and checks.
verify_batch(paths, redact=True) Verify many files. Returns a summary count by indication plus a compact per-file result (indication, trusted, issuing CA).
validate_ci(number) Validate a cédula's check digit (arithmetic consistency only, not an identity check).
doctor(redact=True) Report the local setup status (PC/SC, PKCS#11 module, card, bundled CAs). Every check's status is reported; the card and token details are hidden by default, since some PKCS#11 modules use the cardholder's name as the token label.

verify_batch handles self-contained signatures (PDF/PAdES, XAdES XML). A detached .p7s needs its original file, so verify those one at a time with verify(path, original=...).

Verification is offline, with certificate-chain validation up to the Uruguayan national root. A VALID result is a technical assessment, not a statement of legal validity. For authoritative verification, use AGESIC's official validator. This is an independent, unofficial tool, not affiliated with or endorsed by AGESIC.

Requirements

The firmauy CLI must be installed and on PATH.

uv tool install firmauy
firmauy --version

It is an extra rather than a hard dependency, so you can pin the CLI version yourself or reuse one you already have. To pull it in with the server instead, install the cli extra:

uv tool install firmauy-mcp-inspect --with firmauy    # or: pip install "firmauy-mcp-inspect[cli]"

(Override the executable with the FIRMAUY_BIN environment variable if it lives elsewhere.)

Configuration

All are optional and configured through environment variables.

Variable Default Purpose
FIRMAUY_BIN (found on PATH) Path to the firmauy executable, if it is not on PATH.
FIRMAUY_MCP_TIMEOUT 60 Per-call timeout, in seconds, for each firmauy invocation.
FIRMAUY_MCP_MAX_WORKERS 8 Max concurrent verifications in verify_batch. Set to 1 for sequential.
FIRMAUY_MCP_ALLOWED_ROOTS none (no limit) Directories the tools may read from, separated by the OS path separator (: on Linux/macOS, ; on Windows). When set, any path outside them (after resolving symlinks and ..) is refused.
FIRMAUY_MCP_ALLOWED_EXTENSIONS none (no limit) Comma-separated types allowed for the signed file, e.g. .pdf,.xml,.p7s. Does not restrict a detached .p7s's original.

A malformed numeric override is ignored (it falls back to the default) rather than failing startup.

Sandboxing (recommended)

This server hands file paths from the model to firmauy, which opens them. Confining what it can read matters for a tool that touches national-ID signatures, so set both allowlists. They are opt-in and fail closed.

export FIRMAUY_MCP_ALLOWED_ROOTS="/srv/inbox/signed"
export FIRMAUY_MCP_ALLOWED_EXTENSIONS=".pdf,.xml,.p7s"

Containment is checked on the resolved, canonical path (symlinks and .. followed), so it is not fooled by traversal, symlink escapes, or sibling directories sharing a name prefix. The root allowlist also covers the original of a detached .p7s. The extension allowlist does not, because that original is arbitrary content.

Install

uv tool install firmauy-mcp-inspect

This puts the firmauy-mcp-inspect command on your PATH, which starts the server over stdio. You can also run it without installing, straight from PyPI, with uvx firmauy-mcp-inspect.

Try it interactively with the MCP Inspector.

npx @modelcontextprotocol/inspector firmauy-mcp-inspect

To work on it from a clone instead, run uv sync and then uv run firmauy-mcp-inspect.

Docker

A prebuilt image is published to the GitHub Container Registry on every release, so you can run the server without installing Python or the firmauy CLI.

docker pull ghcr.io/carlosplanchon/firmauy-mcp-inspect:latest

Mount a directory with signed documents and pass any configuration via environment variables:

docker run -i --rm \
  -v /srv/inbox:/srv/inbox:ro \
  -e FIRMAUY_MCP_ALLOWED_ROOTS=/srv/inbox \
  -e FIRMAUY_MCP_ALLOWED_EXTENSIONS=.pdf,.xml,.p7s \
  ghcr.io/carlosplanchon/firmauy-mcp-inspect:latest

Paths you ask the tools to inspect must exist inside the container, so mount the documents (read-only via :ro, since the server only ever reads them, at the same path on both sides) and point FIRMAUY_MCP_ALLOWED_ROOTS at the container path. The container speaks the MCP stdio transport (hence -i), so let your MCP client manage it.

Claude Code

claude mcp add firmauy-inspect -- \
  docker run -i --rm \
  -v /srv/inbox:/srv/inbox:ro \
  -e FIRMAUY_MCP_ALLOWED_ROOTS=/srv/inbox \
  -e FIRMAUY_MCP_ALLOWED_EXTENSIONS=.pdf,.xml,.p7s \
  ghcr.io/carlosplanchon/firmauy-mcp-inspect:latest

Claude Desktop (claude_desktop_config.json)

{
  "mcpServers": {
    "firmauy-inspect": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-v", "/srv/inbox:/srv/inbox:ro",
        "-e", "FIRMAUY_MCP_ALLOWED_ROOTS=/srv/inbox",
        "-e", "FIRMAUY_MCP_ALLOWED_EXTENSIONS=.pdf,.xml,.p7s",
        "ghcr.io/carlosplanchon/firmauy-mcp-inspect:latest"
      ]
    }
  }
}

To build the image yourself instead (for example from a clone), a Dockerfile is included:

docker build -t firmauy-mcp-inspect .

Configure your client

Both forms below assume firmauy-mcp-inspect is on your PATH from uv tool install. If a GUI client cannot find it, use uvx as the command ("command": "uvx", "args": ["firmauy-mcp-inspect"]) or give the absolute path.

Claude Code

claude mcp add firmauy-inspect -- firmauy-mcp-inspect

Claude Desktop (claude_desktop_config.json)

{
  "mcpServers": {
    "firmauy-inspect": {
      "command": "firmauy-mcp-inspect"
    }
  }
}

Then ask it things such as "Verify every signed PDF in this folder, group them by issuing CA, and flag anything that is not VALID or whose chain is not trusted."

Tests

uv run pytest

The tests mock the firmauy subprocess, so they need neither the CLI nor a card. The path-sandboxing tests additionally create temporary files and a symlink on the local filesystem.

License

Apache-2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

firmauy_mcp_inspect-0.2.0.tar.gz (20.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

firmauy_mcp_inspect-0.2.0-py3-none-any.whl (15.0 kB view details)

Uploaded Python 3

File details

Details for the file firmauy_mcp_inspect-0.2.0.tar.gz.

File metadata

  • Download URL: firmauy_mcp_inspect-0.2.0.tar.gz
  • Upload date:
  • Size: 20.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for firmauy_mcp_inspect-0.2.0.tar.gz
Algorithm Hash digest
SHA256 28b67bc5e9bdc2786c5d337975a4e17eddf2cfe21abeb323d7b02b2c40d2ba12
MD5 44cb346b4285822ef44fd3b4248fed73
BLAKE2b-256 fb70c5196b4ef75e1838b35a8fd243af9247019427232c04345f97a0a3003b9d

See more details on using hashes here.

Provenance

The following attestation bundles were made for firmauy_mcp_inspect-0.2.0.tar.gz:

Publisher: release.yml on carlosplanchon/firmauy-mcp-inspect

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file firmauy_mcp_inspect-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for firmauy_mcp_inspect-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 db2e4d6f0228994cf212f3e7b801bb7e4a295c3fe1011e7d98def51652231fa7
MD5 9c23acf1d3d88b799644e623771fdf68
BLAKE2b-256 257fec92d5bf838bd44db60fba41009f9629a45de826d249a8aff2938c3feb47

See more details on using hashes here.

Provenance

The following attestation bundles were made for firmauy_mcp_inspect-0.2.0-py3-none-any.whl:

Publisher: release.yml on carlosplanchon/firmauy-mcp-inspect

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page