Dev.to AI πŸ€– Ai πŸ‘ 0 πŸ“– 3 min read

Turning four compliance and lead-cost checks into a local, read-only MCP server

We run a small call-centre outsourcing brokerage, and a few questions kept coming back to us. Is this telemarketing consent still valid under France's new one-year rule? Which lead source is actually cheapest once you co

We run a small call-centre outsourcing brokerage, and a few questions kept coming back to us. Is this telemarketing consent still valid under France's new one-year rule? Which lead source is actually cheapest once you count the meetings that took place, not the leads? What is missing in a GDPR set-up when customer service is outsourced outside the EU?

Over the last weeks we turned those questions into small, standard-library Python packages. This weekend we wrapped four of them in an MCP server, so that Claude, Cursor and other MCP clients can call them directly: kanibal-tools-mcp.

claude mcp add kanibal-tools -- uvx kanibal-tools-mcp

For Claude Desktop, Cursor or any other client, the configuration is the usual one:

{
  "mcpServers": {
    "kanibal-tools": { "command": "uvx", "args": ["kanibal-tools-mcp"] }
  }
}

The four tools

Tool What it does
check_telemarketing_consent France only: is a consent to telephone canvassing still valid under dΓ©cret 2026-662? Returns valide, expire, revoque or preuve_manquante.
rank_lead_sources_by_kept_meeting Ranks B2B lead sources, from CSV text, on cost per kept qualified meeting instead of cost per lead.
gdpr_offshore_gap_report GDPR gap report for customer service outsourced outside the EU/EEA (transfer basis, transfer impact assessment, Art. 28 clauses, security, breach notification).
select_least_cost_capable_model From a catalogue you supply, picks the model with the lowest declared cost rank among those that have every required capability and are available and authorised.

Each one wraps a package that is published on its own: demarchage-consent, cost-per-kept-meeting, rgpd-offshore-check and least-cost-capable.

Design choices

Local, read-only, closed-world. Every tool computes from its arguments only: no network call, no file access, no state kept between calls. The tools say so to the client through MCP annotations:

LOCAL_READ_ONLY = ToolAnnotations(
    readOnlyHint=True,
    destructiveHint=False,
    idempotentHint=True,
    openWorldHint=False,
)

Fail-closed. A missing or unreadable consent date is preuve_manquante, never valide. In the GDPR checklist, only a JSON true satisfies a check: an unanswered item is reported as "to verify" and still counts as blocking.

Bounded claims. The consent and GDPR tools are checklists, not legal advice. A low cost per kept meeting does not prove profitability. The model picker only uses the facts in the catalogue it receives; it does not call providers or promise savings.

Testing through a real MCP session

The tests do not call the Python functions directly. They go through an in-memory MCP client session, so they also check the tool names, schemas and annotations that a client sees:

from mcp.shared.memory import create_connected_server_and_client_session
from kanibal_tools_mcp.server import mcp

async def list_tools():
    async with create_connected_server_and_client_session(mcp._mcp_server) as client:
        return (await client.list_tools()).tools

One small surprise: FastMCP takes no version argument, so the server reported the SDK version (1.30.0) instead of its own. Setting it on the low-level server fixed it:

mcp._mcp_server.version = __version__

Publishing: PyPI, the MCP Registry, Glama

PyPI uses Trusted Publishing from a release.yml workflow. The publish job runs in a GitHub environment called pypi that requires a maintainer's approval and only accepts v* tags, so no token is stored anywhere.

The official MCP Registry lists the server as io.github.kanibal-studio/kanibal-tools-mcp. Two details mattered:

  • The registry checks ownership of a PyPI package through an mcp-name: io.github.kanibal-studio/kanibal-tools-mcp line in the README that PyPI shows.
  • The interactive mcp-publisher login github flow cannot currently publish under an organisation namespace. Publishing from GitHub Actions with mcp-publisher login github-oidc can, so the release workflow does it right after the PyPI job, and waits until PyPI serves the new version first.

Glama runs its own checks before listing a server. For a repository that belongs to an organisation, it first wants a glama.json at the root naming the maintainers. Its Dockerfile is generated from a small form. The first build failed: uv pip install --system . targeted Debian's Python, which is marked as externally managed. A dedicated virtual environment fixed it:

["uv venv /opt/venv --python 3.12", "uv pip install --python /opt/venv/bin/python ."]

with /opt/venv/bin/python -m kanibal_tools_mcp as the start command. Glama then started the server and listed its four tools.

Try it, and tell us what is missing

We would like feedback on the tool interfaces, especially from people who run outbound or customer-service teams across EU borders.

πŸ“° Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.