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-mcpline in the README that PyPI shows. - The interactive
mcp-publisher login githubflow cannot currently publish under an organisation namespace. Publishing from GitHub Actions withmcp-publisher login github-oidccan, 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
- Code: https://github.com/kanibal-studio/kanibal-tools-mcp (MIT)
- PyPI: https://pypi.org/project/kanibal-tools-mcp/
- Product Hunt: https://www.producthunt.com/products/kanibal-tools-mcp
We would like feedback on the tool interfaces, especially from people who run outbound or customer-service teams across EU borders.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.