kanibal-tools-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kanibal_tools_mcp-0.1.0/LICENSE +21 -0
- kanibal_tools_mcp-0.1.0/PKG-INFO +115 -0
- kanibal_tools_mcp-0.1.0/README.md +81 -0
- kanibal_tools_mcp-0.1.0/kanibal_tools_mcp/__init__.py +3 -0
- kanibal_tools_mcp-0.1.0/kanibal_tools_mcp/__main__.py +3 -0
- kanibal_tools_mcp-0.1.0/kanibal_tools_mcp/server.py +138 -0
- kanibal_tools_mcp-0.1.0/kanibal_tools_mcp.egg-info/PKG-INFO +115 -0
- kanibal_tools_mcp-0.1.0/kanibal_tools_mcp.egg-info/SOURCES.txt +13 -0
- kanibal_tools_mcp-0.1.0/kanibal_tools_mcp.egg-info/dependency_links.txt +1 -0
- kanibal_tools_mcp-0.1.0/kanibal_tools_mcp.egg-info/entry_points.txt +2 -0
- kanibal_tools_mcp-0.1.0/kanibal_tools_mcp.egg-info/requires.txt +8 -0
- kanibal_tools_mcp-0.1.0/kanibal_tools_mcp.egg-info/top_level.txt +1 -0
- kanibal_tools_mcp-0.1.0/pyproject.toml +55 -0
- kanibal_tools_mcp-0.1.0/setup.cfg +4 -0
- kanibal_tools_mcp-0.1.0/tests/test_server.py +129 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ALTA VISTA360, S.L.U.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kanibal-tools-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server with four local, read-only tools: French telemarketing consent, cost per kept meeting, GDPR offshore checklist, least-cost capable model.
|
|
5
|
+
Author: ALTA VISTA360, S.L.U.
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kanibal-studio/kanibal-tools-mcp
|
|
8
|
+
Project-URL: Issues, https://github.com/kanibal-studio/kanibal-tools-mcp/issues
|
|
9
|
+
Project-URL: PyPI, https://pypi.org/project/kanibal-tools-mcp/
|
|
10
|
+
Keywords: mcp,model-context-protocol,gdpr,rgpd,consent,lead-generation,llm,agents
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Classifier: Topic :: Office/Business
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: mcp<2,>=1.12
|
|
27
|
+
Requires-Dist: demarchage-consent>=0.2.0
|
|
28
|
+
Requires-Dist: cost-per-kept-meeting>=0.1.2
|
|
29
|
+
Requires-Dist: rgpd-offshore-check>=0.1.2
|
|
30
|
+
Requires-Dist: least-cost-capable>=0.1.2
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# kanibal-tools-mcp
|
|
36
|
+
|
|
37
|
+
[](https://pypi.org/project/kanibal-tools-mcp/)
|
|
38
|
+
[](https://pypi.org/project/kanibal-tools-mcp/)
|
|
39
|
+
[](https://github.com/kanibal-studio/kanibal-tools-mcp/actions/workflows/ci.yml)
|
|
40
|
+
[](https://github.com/kanibal-studio/kanibal-tools-mcp/blob/main/LICENSE)
|
|
41
|
+
|
|
42
|
+
An [MCP](https://modelcontextprotocol.io) server that gives AI assistants four local,
|
|
43
|
+
read-only tools from Kanibal Studio (ALTA VISTA360, S.L.U.):
|
|
44
|
+
|
|
45
|
+
| Tool | What it does |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `check_telemarketing_consent` | France only: tells whether a consent to telephone canvassing is still valid under décret 2026-662. Fail-closed. |
|
|
48
|
+
| `rank_lead_sources_by_kept_meeting` | Ranks B2B lead sources, from CSV text, on cost per kept qualified meeting instead of cost per lead. |
|
|
49
|
+
| `gdpr_offshore_gap_report` | GDPR gap report for customer service outsourced outside the EU/EEA. Not legal advice. |
|
|
50
|
+
| `select_least_cost_capable_model` | Picks the lowest declared cost rank among the capable, available and authorised models of a supplied catalogue. |
|
|
51
|
+
|
|
52
|
+
Each tool computes from its arguments only: no network call, no file access, no state
|
|
53
|
+
kept between calls. All four are marked read-only and closed-world for MCP clients. They
|
|
54
|
+
wrap the published packages [demarchage-consent](https://github.com/kanibal-studio/demarchage-consent),
|
|
55
|
+
[cost-per-kept-meeting](https://github.com/kanibal-studio/cost-per-kept-meeting),
|
|
56
|
+
[rgpd-offshore-check](https://github.com/kanibal-studio/rgpd-offshore-check) and
|
|
57
|
+
[least-cost-capable](https://github.com/kanibal-studio/least-cost-capable).
|
|
58
|
+
|
|
59
|
+
## Use with Claude Code
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
claude mcp add kanibal-tools -- uvx kanibal-tools-mcp
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Use with Claude Desktop, Cursor or another MCP client
|
|
66
|
+
|
|
67
|
+
Add this server to the client's MCP configuration:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"mcpServers": {
|
|
72
|
+
"kanibal-tools": {
|
|
73
|
+
"command": "uvx",
|
|
74
|
+
"args": ["kanibal-tools-mcp"]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Without `uv`, install it with `pip install kanibal-tools-mcp` and use `kanibal-tools-mcp` as
|
|
81
|
+
the command. The server speaks MCP over stdio and needs Python 3.10 or newer.
|
|
82
|
+
|
|
83
|
+
## Example requests
|
|
84
|
+
|
|
85
|
+
- "Is a telemarketing consent collected on 2025-11-02 still valid today?"
|
|
86
|
+
- "Here is my CSV of lead sources for October: rank them by cost per kept meeting."
|
|
87
|
+
- "Prepare a GDPR gap report for our customer service in Tunisia: we have SCCs but no transfer impact assessment."
|
|
88
|
+
- "From this model catalogue, which is the cheapest one that can do Python and debugging?"
|
|
89
|
+
|
|
90
|
+
## Limits
|
|
91
|
+
|
|
92
|
+
- The consent tool applies French rules only (décret 2026-662). It is a check, not legal advice.
|
|
93
|
+
- The GDPR report is a checklist: a report without blocking gap is not a finding of compliance.
|
|
94
|
+
- A low cost per kept meeting does not prove profitability.
|
|
95
|
+
- The model selection uses only the facts in the catalogue you supply; it does not switch
|
|
96
|
+
models, call providers or guarantee savings.
|
|
97
|
+
|
|
98
|
+
## Development
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
pip install -e ".[dev]"
|
|
102
|
+
pytest
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The tests call every tool through an in-memory MCP client session.
|
|
106
|
+
|
|
107
|
+
## Project context and license
|
|
108
|
+
|
|
109
|
+
Prepared by [Kanibal Studio](https://github.com/kanibal-studio), associated with
|
|
110
|
+
ALTA VISTA360, S.L.U. Our public sites are [Altavista360](https://altavista360.com),
|
|
111
|
+
[Altavista Conseil](https://altavista-conseil.com) and [K-Pulse](https://infos.kanibal-studio.com/).
|
|
112
|
+
|
|
113
|
+
Licensed under the [MIT License](LICENSE).
|
|
114
|
+
|
|
115
|
+
<!-- mcp-name: io.github.kanibal-studio/kanibal-tools-mcp -->
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# kanibal-tools-mcp
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/kanibal-tools-mcp/)
|
|
4
|
+
[](https://pypi.org/project/kanibal-tools-mcp/)
|
|
5
|
+
[](https://github.com/kanibal-studio/kanibal-tools-mcp/actions/workflows/ci.yml)
|
|
6
|
+
[](https://github.com/kanibal-studio/kanibal-tools-mcp/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
An [MCP](https://modelcontextprotocol.io) server that gives AI assistants four local,
|
|
9
|
+
read-only tools from Kanibal Studio (ALTA VISTA360, S.L.U.):
|
|
10
|
+
|
|
11
|
+
| Tool | What it does |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `check_telemarketing_consent` | France only: tells whether a consent to telephone canvassing is still valid under décret 2026-662. Fail-closed. |
|
|
14
|
+
| `rank_lead_sources_by_kept_meeting` | Ranks B2B lead sources, from CSV text, on cost per kept qualified meeting instead of cost per lead. |
|
|
15
|
+
| `gdpr_offshore_gap_report` | GDPR gap report for customer service outsourced outside the EU/EEA. Not legal advice. |
|
|
16
|
+
| `select_least_cost_capable_model` | Picks the lowest declared cost rank among the capable, available and authorised models of a supplied catalogue. |
|
|
17
|
+
|
|
18
|
+
Each tool computes from its arguments only: no network call, no file access, no state
|
|
19
|
+
kept between calls. All four are marked read-only and closed-world for MCP clients. They
|
|
20
|
+
wrap the published packages [demarchage-consent](https://github.com/kanibal-studio/demarchage-consent),
|
|
21
|
+
[cost-per-kept-meeting](https://github.com/kanibal-studio/cost-per-kept-meeting),
|
|
22
|
+
[rgpd-offshore-check](https://github.com/kanibal-studio/rgpd-offshore-check) and
|
|
23
|
+
[least-cost-capable](https://github.com/kanibal-studio/least-cost-capable).
|
|
24
|
+
|
|
25
|
+
## Use with Claude Code
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
claude mcp add kanibal-tools -- uvx kanibal-tools-mcp
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Use with Claude Desktop, Cursor or another MCP client
|
|
32
|
+
|
|
33
|
+
Add this server to the client's MCP configuration:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"mcpServers": {
|
|
38
|
+
"kanibal-tools": {
|
|
39
|
+
"command": "uvx",
|
|
40
|
+
"args": ["kanibal-tools-mcp"]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Without `uv`, install it with `pip install kanibal-tools-mcp` and use `kanibal-tools-mcp` as
|
|
47
|
+
the command. The server speaks MCP over stdio and needs Python 3.10 or newer.
|
|
48
|
+
|
|
49
|
+
## Example requests
|
|
50
|
+
|
|
51
|
+
- "Is a telemarketing consent collected on 2025-11-02 still valid today?"
|
|
52
|
+
- "Here is my CSV of lead sources for October: rank them by cost per kept meeting."
|
|
53
|
+
- "Prepare a GDPR gap report for our customer service in Tunisia: we have SCCs but no transfer impact assessment."
|
|
54
|
+
- "From this model catalogue, which is the cheapest one that can do Python and debugging?"
|
|
55
|
+
|
|
56
|
+
## Limits
|
|
57
|
+
|
|
58
|
+
- The consent tool applies French rules only (décret 2026-662). It is a check, not legal advice.
|
|
59
|
+
- The GDPR report is a checklist: a report without blocking gap is not a finding of compliance.
|
|
60
|
+
- A low cost per kept meeting does not prove profitability.
|
|
61
|
+
- The model selection uses only the facts in the catalogue you supply; it does not switch
|
|
62
|
+
models, call providers or guarantee savings.
|
|
63
|
+
|
|
64
|
+
## Development
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
pip install -e ".[dev]"
|
|
68
|
+
pytest
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The tests call every tool through an in-memory MCP client session.
|
|
72
|
+
|
|
73
|
+
## Project context and license
|
|
74
|
+
|
|
75
|
+
Prepared by [Kanibal Studio](https://github.com/kanibal-studio), associated with
|
|
76
|
+
ALTA VISTA360, S.L.U. Our public sites are [Altavista360](https://altavista360.com),
|
|
77
|
+
[Altavista Conseil](https://altavista-conseil.com) and [K-Pulse](https://infos.kanibal-studio.com/).
|
|
78
|
+
|
|
79
|
+
Licensed under the [MIT License](LICENSE).
|
|
80
|
+
|
|
81
|
+
<!-- mcp-name: io.github.kanibal-studio/kanibal-tools-mcp -->
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""MCP server exposing four local, read-only Kanibal Studio tools.
|
|
2
|
+
|
|
3
|
+
Every tool is a pure computation on the arguments it receives: no network call,
|
|
4
|
+
no file read or write, no state kept between calls. The underlying packages are
|
|
5
|
+
published separately on PyPI (demarchage-consent, cost-per-kept-meeting,
|
|
6
|
+
rgpd-offshore-check, least-cost-capable).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import io
|
|
12
|
+
import json
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from typing import Any, Optional
|
|
15
|
+
|
|
16
|
+
import cost_per_kept_meeting as cpkm
|
|
17
|
+
import demarchage_consent as consent
|
|
18
|
+
import rgpd_offshore_check as rgpd
|
|
19
|
+
from least_cost_capable.select_model import select
|
|
20
|
+
from mcp.server.fastmcp import FastMCP
|
|
21
|
+
from mcp.types import ToolAnnotations
|
|
22
|
+
|
|
23
|
+
from . import __version__
|
|
24
|
+
|
|
25
|
+
LOCAL_READ_ONLY = ToolAnnotations(
|
|
26
|
+
readOnlyHint=True,
|
|
27
|
+
destructiveHint=False,
|
|
28
|
+
idempotentHint=True,
|
|
29
|
+
openWorldHint=False,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
mcp = FastMCP(
|
|
33
|
+
"kanibal-tools",
|
|
34
|
+
instructions=(
|
|
35
|
+
"Local, read-only tools from Kanibal Studio (ALTA VISTA360, S.L.U.). They compute "
|
|
36
|
+
"from the arguments only and never contact a service. The consent and GDPR tools "
|
|
37
|
+
"are checklists, not legal advice."
|
|
38
|
+
),
|
|
39
|
+
website_url="https://github.com/kanibal-studio/kanibal-tools-mcp",
|
|
40
|
+
)
|
|
41
|
+
# FastMCP takes no version argument; without this the server reports the SDK version.
|
|
42
|
+
mcp._mcp_server.version = __version__
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _iso(value: Optional[datetime]) -> Optional[str]:
|
|
46
|
+
return value.isoformat() if value is not None else None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@mcp.tool(annotations=LOCAL_READ_ONLY)
|
|
50
|
+
def check_telemarketing_consent(
|
|
51
|
+
collected_at: str,
|
|
52
|
+
revoked_at: Optional[str] = None,
|
|
53
|
+
now: Optional[str] = None,
|
|
54
|
+
max_age_days: Optional[int] = None,
|
|
55
|
+
) -> dict[str, Any]:
|
|
56
|
+
"""France only: is a consent to telephone canvassing still valid under décret 2026-662?
|
|
57
|
+
|
|
58
|
+
Dates are ISO 8601 (a date without time zone is read as UTC). Statuses:
|
|
59
|
+
"valide", "expire" (older than the ceiling, 365 days at most), "revoque"
|
|
60
|
+
(any sign of revocation wins), "preuve_manquante" (no readable collection
|
|
61
|
+
date, or one in the future). Fail-closed. Not legal advice.
|
|
62
|
+
"""
|
|
63
|
+
result = consent.assess_consent(
|
|
64
|
+
collected_at, now=now, revoked_at=revoked_at, max_age_days=max_age_days
|
|
65
|
+
)
|
|
66
|
+
return {
|
|
67
|
+
"status": str(result.status),
|
|
68
|
+
"evaluated_at": _iso(result.evaluated_at),
|
|
69
|
+
"collected_at": _iso(result.collected_at),
|
|
70
|
+
"revoked_at": _iso(result.revoked_at),
|
|
71
|
+
"max_age_days": result.max_age_days,
|
|
72
|
+
"expires_at": _iso(result.expires_at),
|
|
73
|
+
"proof_retention_until": _iso(result.proof_retention_until),
|
|
74
|
+
"days_remaining": result.days_remaining,
|
|
75
|
+
"decree": consent.DECREE_REFERENCE,
|
|
76
|
+
"tool": f"demarchage-consent {consent.__version__}",
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@mcp.tool(annotations=LOCAL_READ_ONLY)
|
|
81
|
+
def rank_lead_sources_by_kept_meeting(
|
|
82
|
+
csv_text: str, min_meetings: int = cpkm.DEFAULT_MIN_MEETINGS
|
|
83
|
+
) -> dict[str, Any]:
|
|
84
|
+
"""Rank B2B lead sources on cost per kept qualified meeting instead of cost per lead.
|
|
85
|
+
|
|
86
|
+
csv_text is the CSV file content, with the columns documented at
|
|
87
|
+
https://github.com/kanibal-studio/cost-per-kept-meeting#input-columns.
|
|
88
|
+
Results under min_meetings qualified kept meetings are flagged as a small
|
|
89
|
+
sample, never hidden. A low cost per meeting does not prove profitability.
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
table = cpkm.parse_csv(io.StringIO(csv_text))
|
|
93
|
+
except cpkm.InputError as exc:
|
|
94
|
+
raise ValueError("invalid CSV: " + "; ".join(exc.messages)) from exc
|
|
95
|
+
results = cpkm.analyse(table, min_meetings=min_meetings)
|
|
96
|
+
document = json.loads(cpkm.render_json(results, min_meetings))
|
|
97
|
+
document["tool"] = f"cost-per-kept-meeting {cpkm.__version__}"
|
|
98
|
+
return document
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@mcp.tool(annotations=LOCAL_READ_ONLY)
|
|
102
|
+
def gdpr_offshore_gap_report(answers: dict[str, Any], lang: str = "en") -> dict[str, Any]:
|
|
103
|
+
"""GDPR gap report for customer-service processing outsourced outside the EU/EEA.
|
|
104
|
+
|
|
105
|
+
answers follows https://github.com/kanibal-studio/rgpd-offshore-check#input-format.
|
|
106
|
+
Only a JSON true satisfies a check; anything else is reported as a gap or
|
|
107
|
+
"to_verify". lang is "en" or "fr". Not legal advice and not a certification:
|
|
108
|
+
a report without blocking gap is not a finding of compliance.
|
|
109
|
+
"""
|
|
110
|
+
return rgpd.evaluate(answers, lang=lang).to_dict()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@mcp.tool(annotations=LOCAL_READ_ONLY)
|
|
114
|
+
def select_least_cost_capable_model(catalogue: dict[str, Any]) -> dict[str, Any]:
|
|
115
|
+
"""Pick, from a supplied catalogue, the model with the lowest declared cost rank
|
|
116
|
+
among those that have every required capability and are available and authorised.
|
|
117
|
+
|
|
118
|
+
catalogue follows https://github.com/kanibal-studio/least-cost-capable (fields
|
|
119
|
+
required_capabilities and models). Unknown or non-comparable cost evidence stays
|
|
120
|
+
unresolved. Nothing is called, switched or launched; savings are not guaranteed.
|
|
121
|
+
"""
|
|
122
|
+
return select(catalogue)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def main() -> None:
|
|
126
|
+
"""Run the server over stdio (the transport used by desktop MCP clients)."""
|
|
127
|
+
mcp.run()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
__all__ = [
|
|
131
|
+
"__version__",
|
|
132
|
+
"check_telemarketing_consent",
|
|
133
|
+
"gdpr_offshore_gap_report",
|
|
134
|
+
"main",
|
|
135
|
+
"mcp",
|
|
136
|
+
"rank_lead_sources_by_kept_meeting",
|
|
137
|
+
"select_least_cost_capable_model",
|
|
138
|
+
]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kanibal-tools-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server with four local, read-only tools: French telemarketing consent, cost per kept meeting, GDPR offshore checklist, least-cost capable model.
|
|
5
|
+
Author: ALTA VISTA360, S.L.U.
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kanibal-studio/kanibal-tools-mcp
|
|
8
|
+
Project-URL: Issues, https://github.com/kanibal-studio/kanibal-tools-mcp/issues
|
|
9
|
+
Project-URL: PyPI, https://pypi.org/project/kanibal-tools-mcp/
|
|
10
|
+
Keywords: mcp,model-context-protocol,gdpr,rgpd,consent,lead-generation,llm,agents
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Classifier: Topic :: Office/Business
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: mcp<2,>=1.12
|
|
27
|
+
Requires-Dist: demarchage-consent>=0.2.0
|
|
28
|
+
Requires-Dist: cost-per-kept-meeting>=0.1.2
|
|
29
|
+
Requires-Dist: rgpd-offshore-check>=0.1.2
|
|
30
|
+
Requires-Dist: least-cost-capable>=0.1.2
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# kanibal-tools-mcp
|
|
36
|
+
|
|
37
|
+
[](https://pypi.org/project/kanibal-tools-mcp/)
|
|
38
|
+
[](https://pypi.org/project/kanibal-tools-mcp/)
|
|
39
|
+
[](https://github.com/kanibal-studio/kanibal-tools-mcp/actions/workflows/ci.yml)
|
|
40
|
+
[](https://github.com/kanibal-studio/kanibal-tools-mcp/blob/main/LICENSE)
|
|
41
|
+
|
|
42
|
+
An [MCP](https://modelcontextprotocol.io) server that gives AI assistants four local,
|
|
43
|
+
read-only tools from Kanibal Studio (ALTA VISTA360, S.L.U.):
|
|
44
|
+
|
|
45
|
+
| Tool | What it does |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `check_telemarketing_consent` | France only: tells whether a consent to telephone canvassing is still valid under décret 2026-662. Fail-closed. |
|
|
48
|
+
| `rank_lead_sources_by_kept_meeting` | Ranks B2B lead sources, from CSV text, on cost per kept qualified meeting instead of cost per lead. |
|
|
49
|
+
| `gdpr_offshore_gap_report` | GDPR gap report for customer service outsourced outside the EU/EEA. Not legal advice. |
|
|
50
|
+
| `select_least_cost_capable_model` | Picks the lowest declared cost rank among the capable, available and authorised models of a supplied catalogue. |
|
|
51
|
+
|
|
52
|
+
Each tool computes from its arguments only: no network call, no file access, no state
|
|
53
|
+
kept between calls. All four are marked read-only and closed-world for MCP clients. They
|
|
54
|
+
wrap the published packages [demarchage-consent](https://github.com/kanibal-studio/demarchage-consent),
|
|
55
|
+
[cost-per-kept-meeting](https://github.com/kanibal-studio/cost-per-kept-meeting),
|
|
56
|
+
[rgpd-offshore-check](https://github.com/kanibal-studio/rgpd-offshore-check) and
|
|
57
|
+
[least-cost-capable](https://github.com/kanibal-studio/least-cost-capable).
|
|
58
|
+
|
|
59
|
+
## Use with Claude Code
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
claude mcp add kanibal-tools -- uvx kanibal-tools-mcp
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Use with Claude Desktop, Cursor or another MCP client
|
|
66
|
+
|
|
67
|
+
Add this server to the client's MCP configuration:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"mcpServers": {
|
|
72
|
+
"kanibal-tools": {
|
|
73
|
+
"command": "uvx",
|
|
74
|
+
"args": ["kanibal-tools-mcp"]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Without `uv`, install it with `pip install kanibal-tools-mcp` and use `kanibal-tools-mcp` as
|
|
81
|
+
the command. The server speaks MCP over stdio and needs Python 3.10 or newer.
|
|
82
|
+
|
|
83
|
+
## Example requests
|
|
84
|
+
|
|
85
|
+
- "Is a telemarketing consent collected on 2025-11-02 still valid today?"
|
|
86
|
+
- "Here is my CSV of lead sources for October: rank them by cost per kept meeting."
|
|
87
|
+
- "Prepare a GDPR gap report for our customer service in Tunisia: we have SCCs but no transfer impact assessment."
|
|
88
|
+
- "From this model catalogue, which is the cheapest one that can do Python and debugging?"
|
|
89
|
+
|
|
90
|
+
## Limits
|
|
91
|
+
|
|
92
|
+
- The consent tool applies French rules only (décret 2026-662). It is a check, not legal advice.
|
|
93
|
+
- The GDPR report is a checklist: a report without blocking gap is not a finding of compliance.
|
|
94
|
+
- A low cost per kept meeting does not prove profitability.
|
|
95
|
+
- The model selection uses only the facts in the catalogue you supply; it does not switch
|
|
96
|
+
models, call providers or guarantee savings.
|
|
97
|
+
|
|
98
|
+
## Development
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
pip install -e ".[dev]"
|
|
102
|
+
pytest
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The tests call every tool through an in-memory MCP client session.
|
|
106
|
+
|
|
107
|
+
## Project context and license
|
|
108
|
+
|
|
109
|
+
Prepared by [Kanibal Studio](https://github.com/kanibal-studio), associated with
|
|
110
|
+
ALTA VISTA360, S.L.U. Our public sites are [Altavista360](https://altavista360.com),
|
|
111
|
+
[Altavista Conseil](https://altavista-conseil.com) and [K-Pulse](https://infos.kanibal-studio.com/).
|
|
112
|
+
|
|
113
|
+
Licensed under the [MIT License](LICENSE).
|
|
114
|
+
|
|
115
|
+
<!-- mcp-name: io.github.kanibal-studio/kanibal-tools-mcp -->
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
kanibal_tools_mcp/__init__.py
|
|
5
|
+
kanibal_tools_mcp/__main__.py
|
|
6
|
+
kanibal_tools_mcp/server.py
|
|
7
|
+
kanibal_tools_mcp.egg-info/PKG-INFO
|
|
8
|
+
kanibal_tools_mcp.egg-info/SOURCES.txt
|
|
9
|
+
kanibal_tools_mcp.egg-info/dependency_links.txt
|
|
10
|
+
kanibal_tools_mcp.egg-info/entry_points.txt
|
|
11
|
+
kanibal_tools_mcp.egg-info/requires.txt
|
|
12
|
+
kanibal_tools_mcp.egg-info/top_level.txt
|
|
13
|
+
tests/test_server.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
kanibal_tools_mcp
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kanibal-tools-mcp"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "MCP server with four local, read-only tools: French telemarketing consent, cost per kept meeting, GDPR offshore checklist, least-cost capable model."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "ALTA VISTA360, S.L.U." }]
|
|
14
|
+
keywords = ["mcp", "model-context-protocol", "gdpr", "rgpd", "consent", "lead-generation", "llm", "agents"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Programming Language :: Python :: 3.14",
|
|
26
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
27
|
+
"Topic :: Office/Business",
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"mcp>=1.12,<2",
|
|
31
|
+
"demarchage-consent>=0.2.0",
|
|
32
|
+
"cost-per-kept-meeting>=0.1.2",
|
|
33
|
+
"rgpd-offshore-check>=0.1.2",
|
|
34
|
+
"least-cost-capable>=0.1.2",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
dev = ["pytest>=8"]
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
kanibal-tools-mcp = "kanibal_tools_mcp.server:main"
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://github.com/kanibal-studio/kanibal-tools-mcp"
|
|
45
|
+
Issues = "https://github.com/kanibal-studio/kanibal-tools-mcp/issues"
|
|
46
|
+
PyPI = "https://pypi.org/project/kanibal-tools-mcp/"
|
|
47
|
+
|
|
48
|
+
[tool.setuptools]
|
|
49
|
+
packages = ["kanibal_tools_mcp"]
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.dynamic]
|
|
52
|
+
version = { attr = "kanibal_tools_mcp.__version__" }
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""The four tools, called through a real MCP client session held in memory."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
from mcp.shared.memory import create_connected_server_and_client_session
|
|
9
|
+
|
|
10
|
+
from kanibal_tools_mcp import __version__
|
|
11
|
+
from kanibal_tools_mcp.server import mcp
|
|
12
|
+
|
|
13
|
+
CSV = """source,period,cost_media,cost_data,cost_tools,cost_human_time,cost_other,leads,meetings_booked,meetings_kept,meetings_qualified_kept,deals_closed,gross_margin
|
|
14
|
+
Example Purchased Lists,2026-10,0,2400,300,1800,0,1500,60,33,9,1,2500
|
|
15
|
+
Example Partner Referrals,2026-10,0,0,150,900,1650,60,24,21,15,4,11000
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
COST = {"metric": "API input-token cost", "unit": "USD per million input tokens",
|
|
19
|
+
"workload": "same coding task mix", "window": "2026-10-01 to 2026-10-11",
|
|
20
|
+
"source": "synthetic example", "as_of": "2026-10-11"}
|
|
21
|
+
CATALOGUE = {
|
|
22
|
+
"required_capabilities": ["python", "debugging"],
|
|
23
|
+
"models": [
|
|
24
|
+
{"id": "economy-text", "available": True, "authorized": True,
|
|
25
|
+
"capabilities": ["python"], "capabilities_complete": True,
|
|
26
|
+
"cost": {**COST, "relative_rank": 1}},
|
|
27
|
+
{"id": "standard-code", "available": True, "authorized": True,
|
|
28
|
+
"capabilities": ["python", "debugging"], "capabilities_complete": True,
|
|
29
|
+
"cost": {**COST, "relative_rank": 2}},
|
|
30
|
+
],
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _call(name: str, arguments: dict) -> tuple[bool, object]:
|
|
35
|
+
async def run():
|
|
36
|
+
async with create_connected_server_and_client_session(mcp._mcp_server) as client:
|
|
37
|
+
result = await client.call_tool(name, arguments)
|
|
38
|
+
text = "".join(c.text for c in result.content if getattr(c, "type", "") == "text")
|
|
39
|
+
payload = result.structuredContent
|
|
40
|
+
if payload is None and not result.isError:
|
|
41
|
+
payload = json.loads(text)
|
|
42
|
+
return result.isError, (text if result.isError else payload)
|
|
43
|
+
|
|
44
|
+
return asyncio.run(run())
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _unwrap(payload: object) -> dict:
|
|
48
|
+
# FastMCP wraps non-object structured output as {"result": ...}.
|
|
49
|
+
if isinstance(payload, dict) and set(payload) == {"result"}:
|
|
50
|
+
return payload["result"]
|
|
51
|
+
return payload
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_lists_exactly_four_read_only_local_tools():
|
|
55
|
+
async def run():
|
|
56
|
+
async with create_connected_server_and_client_session(mcp._mcp_server) as client:
|
|
57
|
+
return (await client.list_tools()).tools
|
|
58
|
+
|
|
59
|
+
tools = asyncio.run(run())
|
|
60
|
+
assert sorted(t.name for t in tools) == [
|
|
61
|
+
"check_telemarketing_consent",
|
|
62
|
+
"gdpr_offshore_gap_report",
|
|
63
|
+
"rank_lead_sources_by_kept_meeting",
|
|
64
|
+
"select_least_cost_capable_model",
|
|
65
|
+
]
|
|
66
|
+
for tool in tools:
|
|
67
|
+
assert tool.annotations.readOnlyHint is True
|
|
68
|
+
assert tool.annotations.openWorldHint is False
|
|
69
|
+
assert tool.annotations.destructiveHint is False
|
|
70
|
+
assert tool.description and len(tool.description) > 40
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_consent_statuses_are_fail_closed():
|
|
74
|
+
error, valid = _call(
|
|
75
|
+
"check_telemarketing_consent",
|
|
76
|
+
{"collected_at": "2026-03-01", "now": "2026-10-01"},
|
|
77
|
+
)
|
|
78
|
+
assert not error and _unwrap(valid)["status"] == "valide"
|
|
79
|
+
assert _unwrap(valid)["days_remaining"] > 0
|
|
80
|
+
|
|
81
|
+
_, expired = _call(
|
|
82
|
+
"check_telemarketing_consent",
|
|
83
|
+
{"collected_at": "2025-01-01", "now": "2026-10-01"},
|
|
84
|
+
)
|
|
85
|
+
assert _unwrap(expired)["status"] == "expire"
|
|
86
|
+
|
|
87
|
+
_, missing = _call("check_telemarketing_consent", {"collected_at": "not a date"})
|
|
88
|
+
assert _unwrap(missing)["status"] == "preuve_manquante"
|
|
89
|
+
|
|
90
|
+
_, revoked = _call(
|
|
91
|
+
"check_telemarketing_consent",
|
|
92
|
+
{"collected_at": "2026-09-01", "revoked_at": "oui", "now": "2026-10-01"},
|
|
93
|
+
)
|
|
94
|
+
assert _unwrap(revoked)["status"] == "revoque"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_ranking_puts_referrals_before_purchased_lists():
|
|
98
|
+
error, payload = _call("rank_lead_sources_by_kept_meeting", {"csv_text": CSV})
|
|
99
|
+
assert not error
|
|
100
|
+
results = _unwrap(payload)["results"]
|
|
101
|
+
assert [r["source"] for r in results][0] == "Example Partner Referrals"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_invalid_csv_is_a_named_tool_error_not_a_crash():
|
|
105
|
+
error, text = _call("rank_lead_sources_by_kept_meeting", {"csv_text": "a,b\n1,2\n"})
|
|
106
|
+
assert error and "invalid CSV" in text
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_gdpr_report_keeps_disclaimer_and_flags_unanswered_items():
|
|
110
|
+
error, payload = _call(
|
|
111
|
+
"gdpr_offshore_gap_report",
|
|
112
|
+
{"answers": {"destination_country": "Tunisia"}, "lang": "fr"},
|
|
113
|
+
)
|
|
114
|
+
report = _unwrap(payload)
|
|
115
|
+
assert not error and report["lang"] == "fr"
|
|
116
|
+
assert report["disclaimer"]
|
|
117
|
+
# Unanswered items are "to_verify", never compliant, and still block (fail-closed).
|
|
118
|
+
assert report["summary"]["result"] == "blocking_gap"
|
|
119
|
+
assert len(report["summary"]["blocking_to_verify"]) > 0
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_model_selection_uses_the_capability_filter():
|
|
123
|
+
error, payload = _call("select_least_cost_capable_model", {"catalogue": CATALOGUE})
|
|
124
|
+
assert not error and _unwrap(payload)["selected"] == "standard-code"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_version_is_exposed_to_clients():
|
|
128
|
+
assert __version__ == "0.1.0"
|
|
129
|
+
assert mcp._mcp_server.version == __version__
|