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.
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/kanibal-tools-mcp)](https://pypi.org/project/kanibal-tools-mcp/)
38
+ [![Python](https://img.shields.io/pypi/pyversions/kanibal-tools-mcp)](https://pypi.org/project/kanibal-tools-mcp/)
39
+ [![CI](https://github.com/kanibal-studio/kanibal-tools-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/kanibal-studio/kanibal-tools-mcp/actions/workflows/ci.yml)
40
+ [![MIT](https://img.shields.io/badge/license-MIT-blue)](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
+ [![PyPI](https://img.shields.io/pypi/v/kanibal-tools-mcp)](https://pypi.org/project/kanibal-tools-mcp/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/kanibal-tools-mcp)](https://pypi.org/project/kanibal-tools-mcp/)
5
+ [![CI](https://github.com/kanibal-studio/kanibal-tools-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/kanibal-studio/kanibal-tools-mcp/actions/workflows/ci.yml)
6
+ [![MIT](https://img.shields.io/badge/license-MIT-blue)](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,3 @@
1
+ """kanibal-tools-mcp: MCP server for four local, read-only Kanibal Studio tools."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,3 @@
1
+ from .server import main
2
+
3
+ main()
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/kanibal-tools-mcp)](https://pypi.org/project/kanibal-tools-mcp/)
38
+ [![Python](https://img.shields.io/pypi/pyversions/kanibal-tools-mcp)](https://pypi.org/project/kanibal-tools-mcp/)
39
+ [![CI](https://github.com/kanibal-studio/kanibal-tools-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/kanibal-studio/kanibal-tools-mcp/actions/workflows/ci.yml)
40
+ [![MIT](https://img.shields.io/badge/license-MIT-blue)](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,2 @@
1
+ [console_scripts]
2
+ kanibal-tools-mcp = kanibal_tools_mcp.server:main
@@ -0,0 +1,8 @@
1
+ mcp<2,>=1.12
2
+ demarchage-consent>=0.2.0
3
+ cost-per-kept-meeting>=0.1.2
4
+ rgpd-offshore-check>=0.1.2
5
+ least-cost-capable>=0.1.2
6
+
7
+ [dev]
8
+ pytest>=8
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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__