cleanlib-mcp-server 0.2.0__py3-none-any.whl
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.
- cleanlib_mcp/__init__.py +8 -0
- cleanlib_mcp/backend.py +221 -0
- cleanlib_mcp/server.py +128 -0
- cleanlib_mcp_server-0.2.0.dist-info/METADATA +109 -0
- cleanlib_mcp_server-0.2.0.dist-info/RECORD +9 -0
- cleanlib_mcp_server-0.2.0.dist-info/WHEEL +5 -0
- cleanlib_mcp_server-0.2.0.dist-info/entry_points.txt +2 -0
- cleanlib_mcp_server-0.2.0.dist-info/licenses/LICENSE +15 -0
- cleanlib_mcp_server-0.2.0.dist-info/top_level.txt +1 -0
cleanlib_mcp/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""CleanLibrary MCP server.
|
|
2
|
+
|
|
3
|
+
Exposes CleanLibrary verdict-aware supply-chain risk assessment as MCP tools
|
|
4
|
+
so MCP-capable clients (Claude Code, Claude Desktop, Cursor, GitHub Copilot,
|
|
5
|
+
and other agents) can fetch package verdicts directly.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.2.0"
|
cleanlib_mcp/backend.py
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""Verdict backend.
|
|
2
|
+
|
|
3
|
+
When CLEANLIB_ENDPOINT + CLEANLIB_API_KEY are configured, fetches verdicts
|
|
4
|
+
from the CleanLibrary App `GET /v1/customer/verdicts/{eco}/{pkg}/{ver}`.
|
|
5
|
+
When unconfigured (or the endpoint is unreachable), returns bundled demo
|
|
6
|
+
fixtures so MCP clients always receive useful output.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import secrets
|
|
13
|
+
from dataclasses import asdict, dataclass
|
|
14
|
+
from typing import Literal
|
|
15
|
+
|
|
16
|
+
import httpx
|
|
17
|
+
|
|
18
|
+
Decision = Literal["ALLOW", "DENY", "WARN"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class Verdict:
|
|
23
|
+
"""Mirrors the CleanLibrary App GET /v1/customer/verdicts response shape."""
|
|
24
|
+
|
|
25
|
+
verdict_id: str
|
|
26
|
+
ecosystem: str
|
|
27
|
+
package: str
|
|
28
|
+
version: str
|
|
29
|
+
decision: Decision
|
|
30
|
+
reasoning: str | None = None
|
|
31
|
+
confidence: float | None = None
|
|
32
|
+
source: str | None = None
|
|
33
|
+
|
|
34
|
+
def to_dict(self) -> dict:
|
|
35
|
+
return {k: v for k, v in asdict(self).items() if v is not None}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Bundled demo fixtures (cors-2.8.4 DENY / cors-2.8.5 ALLOW / helmet ALLOW /
|
|
39
|
+
# dotenv ALLOW / oracledb WARN) used when no live backend is configured.
|
|
40
|
+
_MOCK_FIXTURES: dict[str, dict] = {
|
|
41
|
+
"npm/cors/2.8.4": {
|
|
42
|
+
"decision": "DENY",
|
|
43
|
+
"reasoning": "Policy rule: cors-2.8.4 has known prototype pollution; upgrade to 2.8.5",
|
|
44
|
+
"confidence": 0.98,
|
|
45
|
+
"source": "VECTOR_VERDICT",
|
|
46
|
+
},
|
|
47
|
+
"npm/cors/2.8.5": {
|
|
48
|
+
"decision": "ALLOW",
|
|
49
|
+
"reasoning": "No findings; matches policy allowlist",
|
|
50
|
+
"confidence": 0.95,
|
|
51
|
+
"source": "ALLOWED_NO_FINDINGS",
|
|
52
|
+
},
|
|
53
|
+
"npm/helmet/8.0.0": {
|
|
54
|
+
"decision": "ALLOW",
|
|
55
|
+
"reasoning": "No findings; standard security middleware",
|
|
56
|
+
"confidence": 0.97,
|
|
57
|
+
"source": "ALLOWED_NO_FINDINGS",
|
|
58
|
+
},
|
|
59
|
+
"npm/dotenv/16.4.5": {
|
|
60
|
+
"decision": "ALLOW",
|
|
61
|
+
"reasoning": "No findings; widely-used config loader",
|
|
62
|
+
"confidence": 0.94,
|
|
63
|
+
"source": "ALLOWED_NO_FINDINGS",
|
|
64
|
+
},
|
|
65
|
+
"npm/oracledb/6.5.1": {
|
|
66
|
+
"decision": "WARN",
|
|
67
|
+
"reasoning": "Vendor-binary present; review supply-chain provenance before deploy",
|
|
68
|
+
"confidence": 0.70,
|
|
69
|
+
"source": "INSUFFICIENT_DATA",
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
async def fetch_verdict(ecosystem: str, package: str, version: str) -> Verdict:
|
|
75
|
+
"""Fetch verdict for a package version.
|
|
76
|
+
|
|
77
|
+
Fixture-mode (default): returns a bundled demo fixture, or default-ALLOW
|
|
78
|
+
for unknown packages.
|
|
79
|
+
|
|
80
|
+
Live-mode (when CLEANLIB_ENDPOINT + CLEANLIB_API_KEY env set): queries the
|
|
81
|
+
CleanLibrary App customer-verdict endpoint, falling back to a fixture if
|
|
82
|
+
the endpoint is unreachable so the client always receives output.
|
|
83
|
+
"""
|
|
84
|
+
endpoint = os.getenv("CLEANLIB_ENDPOINT", "").strip()
|
|
85
|
+
api_key = os.getenv("CLEANLIB_API_KEY", "").strip()
|
|
86
|
+
|
|
87
|
+
if endpoint and api_key:
|
|
88
|
+
try:
|
|
89
|
+
return await _live_fetch(endpoint, api_key, ecosystem, package, version)
|
|
90
|
+
except LiveBackendNotDeployed:
|
|
91
|
+
# Configured endpoint unreachable — fall back to a fixture so the
|
|
92
|
+
# client still receives useful output. The fallback verdict carries
|
|
93
|
+
# a reasoning string surfacing that the live backend was unavailable.
|
|
94
|
+
v = _mock_fetch(ecosystem, package, version)
|
|
95
|
+
v.reasoning = (
|
|
96
|
+
f"[live-mode fallback: CleanLibrary backend at {endpoint} unavailable] "
|
|
97
|
+
+ (v.reasoning or "")
|
|
98
|
+
)
|
|
99
|
+
return v
|
|
100
|
+
return _mock_fetch(ecosystem, package, version)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _mock_fetch(ecosystem: str, package: str, version: str) -> Verdict:
|
|
104
|
+
key = f"{ecosystem}/{package}/{version}"
|
|
105
|
+
verdict_id = f"mock_{secrets.token_hex(4)}"
|
|
106
|
+
fixture = _MOCK_FIXTURES.get(key)
|
|
107
|
+
if fixture is not None:
|
|
108
|
+
return Verdict(
|
|
109
|
+
verdict_id=verdict_id,
|
|
110
|
+
ecosystem=ecosystem,
|
|
111
|
+
package=package,
|
|
112
|
+
version=version,
|
|
113
|
+
**fixture,
|
|
114
|
+
)
|
|
115
|
+
return Verdict(
|
|
116
|
+
verdict_id=verdict_id,
|
|
117
|
+
ecosystem=ecosystem,
|
|
118
|
+
package=package,
|
|
119
|
+
version=version,
|
|
120
|
+
decision="ALLOW",
|
|
121
|
+
reasoning="Default-ALLOW (no fixture matched; live App returned no verdict for this package version)",
|
|
122
|
+
confidence=0.5,
|
|
123
|
+
source="INSUFFICIENT_DATA",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class LiveBackendNotDeployed(Exception):
|
|
128
|
+
"""Raised when CLEANLIB_ENDPOINT is set but the verdict endpoint is
|
|
129
|
+
unreachable (empty-body 404). Distinguished from "package has no verdict"
|
|
130
|
+
(a structured-JSON 404) by response content-type. The tool layer treats
|
|
131
|
+
this as a degraded-mode signal and falls back to a fixture so clients
|
|
132
|
+
receive useful output even when the live backend is unavailable."""
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
async def _live_fetch(
|
|
136
|
+
endpoint: str, api_key: str, ecosystem: str, package: str, version: str
|
|
137
|
+
) -> Verdict:
|
|
138
|
+
"""Live `GET /v1/customer/verdicts/{eco}/{pkg}/{ver}` call against the
|
|
139
|
+
configured CleanLibrary App.
|
|
140
|
+
|
|
141
|
+
A structured-JSON 404 means "verdict not found" for that package version;
|
|
142
|
+
an empty-body 404 means the endpoint is unreachable. The wrapper
|
|
143
|
+
distinguishes the two by response content-type and falls back to a
|
|
144
|
+
fixture in the unreachable case.
|
|
145
|
+
"""
|
|
146
|
+
url = f"{endpoint.rstrip('/')}/v1/customer/verdicts/{ecosystem}/{package}/{version}"
|
|
147
|
+
headers = {
|
|
148
|
+
"authorization": f"Bearer {api_key}",
|
|
149
|
+
"accept": "application/json",
|
|
150
|
+
"user-agent": "cleanlib-mcp-server/0.2.0",
|
|
151
|
+
}
|
|
152
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
153
|
+
try:
|
|
154
|
+
resp = await client.get(url, headers=headers)
|
|
155
|
+
except httpx.HTTPError as e:
|
|
156
|
+
raise RuntimeError(f"live App transport failure: {e}") from e
|
|
157
|
+
|
|
158
|
+
if resp.status_code == 200:
|
|
159
|
+
data = resp.json()
|
|
160
|
+
return Verdict(
|
|
161
|
+
verdict_id=data.get("verdict_id", ""),
|
|
162
|
+
ecosystem=data.get("ecosystem", ecosystem),
|
|
163
|
+
package=data.get("package", package),
|
|
164
|
+
version=data.get("version", version),
|
|
165
|
+
decision=data.get("decision", "ALLOW"),
|
|
166
|
+
reasoning=data.get("reasoning"),
|
|
167
|
+
confidence=data.get("confidence"),
|
|
168
|
+
source=data.get("source"),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
if resp.status_code == 404:
|
|
172
|
+
# Two cases: (a) endpoint reached but no verdict for this package
|
|
173
|
+
# (structured-JSON body); (b) endpoint unreachable (empty body or
|
|
174
|
+
# text/html). Distinguish via content-type.
|
|
175
|
+
content_type = resp.headers.get("content-type", "")
|
|
176
|
+
if "json" in content_type and resp.content:
|
|
177
|
+
# "Verdict not found" — return an INSUFFICIENT_DATA verdict.
|
|
178
|
+
return Verdict(
|
|
179
|
+
verdict_id=f"live_404_{secrets.token_hex(4)}",
|
|
180
|
+
ecosystem=ecosystem,
|
|
181
|
+
package=package,
|
|
182
|
+
version=version,
|
|
183
|
+
decision="ALLOW",
|
|
184
|
+
reasoning=f"No verdict on file for {ecosystem}/{package}@{version}",
|
|
185
|
+
confidence=None,
|
|
186
|
+
source="INSUFFICIENT_DATA",
|
|
187
|
+
)
|
|
188
|
+
raise LiveBackendNotDeployed(
|
|
189
|
+
f"CleanLibrary backend at {endpoint} did not return a verdict for "
|
|
190
|
+
f"/v1/customer/verdicts/{ecosystem}/{package}/{version}; "
|
|
191
|
+
f"returning fixture fallback."
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if resp.status_code == 401 or resp.status_code == 403:
|
|
195
|
+
raise PermissionError(
|
|
196
|
+
f"App rejected Bearer (status={resp.status_code}); check CLEANLIB_API_KEY "
|
|
197
|
+
f"is valid for {endpoint}"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
raise RuntimeError(f"App returned unexpected status {resp.status_code}: {resp.text[:200]}")
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
async def _validate_live_endpoint_reachable(endpoint: str) -> bool:
|
|
204
|
+
"""Reachability probe via /health (public; no auth required). Used by
|
|
205
|
+
`cleanlib_health_check` MCP tool to verify MCP→App transport works
|
|
206
|
+
end-to-end even when the verdict endpoint isn't yet deployed."""
|
|
207
|
+
try:
|
|
208
|
+
async with httpx.AsyncClient(timeout=5.0) as client:
|
|
209
|
+
resp = await client.get(f"{endpoint.rstrip('/')}/health")
|
|
210
|
+
return resp.status_code == 200
|
|
211
|
+
except Exception:
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
async def live_health(endpoint: str) -> dict:
|
|
216
|
+
"""Fetch the App's /health response (JSON) — used by the
|
|
217
|
+
`cleanlib_health_check` MCP tool. Public endpoint; no auth needed."""
|
|
218
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
219
|
+
resp = await client.get(f"{endpoint.rstrip('/')}/health")
|
|
220
|
+
resp.raise_for_status()
|
|
221
|
+
return resp.json()
|
cleanlib_mcp/server.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""MCP server entry-point — registers CleanLibrary tools and serves over
|
|
2
|
+
stdio transport. Compatible with any MCP-capable client (Claude Code,
|
|
3
|
+
Claude Desktop, Cursor, GitHub Copilot, and other agents).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
from mcp.server import Server
|
|
12
|
+
from mcp.server.stdio import stdio_server
|
|
13
|
+
from mcp.types import TextContent, Tool
|
|
14
|
+
|
|
15
|
+
from .backend import fetch_verdict, live_health
|
|
16
|
+
|
|
17
|
+
log = logging.getLogger("cleanlib-mcp-server")
|
|
18
|
+
|
|
19
|
+
server: Server = Server("cleanlib-mcp-server")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@server.list_tools()
|
|
23
|
+
async def list_tools() -> list[Tool]:
|
|
24
|
+
return [
|
|
25
|
+
Tool(
|
|
26
|
+
name="cleanlib_fetch_verdict",
|
|
27
|
+
description=(
|
|
28
|
+
"Fetch the CleanLibrary verdict for a package version. Returns the "
|
|
29
|
+
"decision (ALLOW / DENY / WARN), reasoning, and confidence. Queries a "
|
|
30
|
+
"live CleanLibrary backend when CLEANLIB_ENDPOINT + CLEANLIB_API_KEY "
|
|
31
|
+
"are configured; otherwise returns bundled demo fixtures so the tool "
|
|
32
|
+
"always responds."
|
|
33
|
+
),
|
|
34
|
+
inputSchema={
|
|
35
|
+
"type": "object",
|
|
36
|
+
"properties": {
|
|
37
|
+
"ecosystem": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"enum": ["npm", "pypi", "go", "maven", "crates", "nuget", "rubygems"],
|
|
40
|
+
"description": "Package ecosystem",
|
|
41
|
+
},
|
|
42
|
+
"package": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "Package name (e.g. lodash, requests, github.com/spf13/cobra)",
|
|
45
|
+
},
|
|
46
|
+
"version": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"description": "Semantic version (e.g. 4.17.21)",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
"required": ["ecosystem", "package", "version"],
|
|
52
|
+
},
|
|
53
|
+
),
|
|
54
|
+
Tool(
|
|
55
|
+
name="cleanlib_health_check",
|
|
56
|
+
description=(
|
|
57
|
+
"Verify MCP→App transport reachability via the public /health "
|
|
58
|
+
"endpoint. Returns the App's health JSON ({status, service, version, "
|
|
59
|
+
"ecosystems_mounted}). Requires CLEANLIB_ENDPOINT env var. Public "
|
|
60
|
+
"endpoint; no API key needed. Use this to confirm MCP server can "
|
|
61
|
+
"reach the deployed App before invoking verdict fetches."
|
|
62
|
+
),
|
|
63
|
+
inputSchema={
|
|
64
|
+
"type": "object",
|
|
65
|
+
"properties": {},
|
|
66
|
+
"required": [],
|
|
67
|
+
},
|
|
68
|
+
),
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@server.call_tool()
|
|
73
|
+
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
|
|
74
|
+
if name == "cleanlib_fetch_verdict":
|
|
75
|
+
ecosystem = arguments.get("ecosystem", "")
|
|
76
|
+
package = arguments.get("package", "")
|
|
77
|
+
version = arguments.get("version", "")
|
|
78
|
+
if not (ecosystem and package and version):
|
|
79
|
+
return [TextContent(type="text", text="Error: ecosystem, package, and version are all required.")]
|
|
80
|
+
try:
|
|
81
|
+
verdict = await fetch_verdict(ecosystem, package, version)
|
|
82
|
+
except PermissionError as e:
|
|
83
|
+
return [TextContent(type="text", text=f"Auth error: {e}")]
|
|
84
|
+
except Exception as e:
|
|
85
|
+
return [TextContent(type="text", text=f"Verdict fetch failed: {e}")]
|
|
86
|
+
# Render as concise human-readable summary + JSON detail for agents.
|
|
87
|
+
icon = "✓" if verdict.decision == "ALLOW" else "✗" if verdict.decision == "DENY" else "⚠"
|
|
88
|
+
confidence_str = f"{verdict.confidence:.0%}" if verdict.confidence is not None else "n/a"
|
|
89
|
+
summary = (
|
|
90
|
+
f"{icon} {verdict.ecosystem}/{verdict.package}@{verdict.version} → "
|
|
91
|
+
f"{verdict.decision} (confidence {confidence_str})\n\n"
|
|
92
|
+
f"Reasoning: {verdict.reasoning or '(none)'}\n\n"
|
|
93
|
+
f"verdict_id: {verdict.verdict_id}"
|
|
94
|
+
)
|
|
95
|
+
return [TextContent(type="text", text=summary)]
|
|
96
|
+
|
|
97
|
+
if name == "cleanlib_health_check":
|
|
98
|
+
import os
|
|
99
|
+
endpoint = os.getenv("CLEANLIB_ENDPOINT", "").strip()
|
|
100
|
+
if not endpoint:
|
|
101
|
+
return [TextContent(type="text", text="CLEANLIB_ENDPOINT env var not set; cannot probe.")]
|
|
102
|
+
try:
|
|
103
|
+
data = await live_health(endpoint)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
return [TextContent(type="text", text=f"Health probe failed against {endpoint}: {e}")]
|
|
106
|
+
return [TextContent(type="text", text=(
|
|
107
|
+
f"✓ CleanLibrary App reachable at {endpoint}\n\n"
|
|
108
|
+
f"status: {data.get('status')}\n"
|
|
109
|
+
f"service: {data.get('service')}\n"
|
|
110
|
+
f"version: {data.get('version')}\n"
|
|
111
|
+
f"ecosystems_mounted: {', '.join(data.get('ecosystems_mounted', []))}"
|
|
112
|
+
))]
|
|
113
|
+
|
|
114
|
+
return [TextContent(type="text", text=f"Unknown tool: {name}")]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def serve() -> None:
|
|
118
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
119
|
+
await server.run(read_stream, write_stream, server.create_initialization_options())
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def main() -> None:
|
|
123
|
+
logging.basicConfig(level=logging.INFO)
|
|
124
|
+
asyncio.run(serve())
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
main()
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cleanlib-mcp-server
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: CleanLibrary MCP server — exposes verdict-aware supply-chain risk assessment as Model Context Protocol tools for AI agent workflows
|
|
5
|
+
Author-email: CleanStart Inc <cto.office@cleanstart.com>
|
|
6
|
+
Maintainer-email: CleanStart Inc <cto.office@cleanstart.com>
|
|
7
|
+
License: CleanStart Inc Proprietary
|
|
8
|
+
Project-URL: Homepage, https://cleanlibrary.clnstrt.dev
|
|
9
|
+
Project-URL: Documentation, https://cleanlibrary.clnstrt.dev
|
|
10
|
+
Keywords: cleanlibrary,mcp,model-context-protocol,cleanstart,supply-chain-security,verdict,policy-evaluation
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Information Technology
|
|
14
|
+
Classifier: License :: Other/Proprietary License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Security
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: System :: Software Distribution
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: mcp>=1.0.0
|
|
28
|
+
Requires-Dist: httpx>=0.27.0
|
|
29
|
+
Requires-Dist: pydantic>=2.7.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
33
|
+
Requires-Dist: ruff>=0.6.0; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# cleanlib-mcp-server
|
|
37
|
+
|
|
38
|
+
CleanLibrary MCP (Model Context Protocol) server — expose verdict-aware supply-chain risk assessment as MCP tools, so MCP-capable clients (Claude Code, Claude Desktop, Cursor, GitHub Copilot, and other agents) can fetch package verdicts directly inside the developer's workflow.
|
|
39
|
+
|
|
40
|
+
Ask your AI assistant *"is cors@2.8.4 safe to install?"* and it queries CleanLibrary for an `ALLOW` / `DENY` / `WARN` verdict with reasoning and confidence — without leaving the editor.
|
|
41
|
+
|
|
42
|
+
## Tools
|
|
43
|
+
|
|
44
|
+
| Tool | Description |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `cleanlib_fetch_verdict(ecosystem, package, version)` | Fetch a verdict (`ALLOW` / `DENY` / `WARN`) with reasoning and confidence for a package version |
|
|
47
|
+
| `cleanlib_health_check()` | Report server status + whether a live CleanLibrary backend is configured |
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install cleanlib-mcp-server
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Run
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
cleanlib-mcp-server # stdio transport (per MCP spec)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Backend modes
|
|
62
|
+
|
|
63
|
+
- **Connected** — when `CLEANLIB_ENDPOINT` + `CLEANLIB_API_KEY` are set, the server queries your CleanLibrary deployment for live verdicts.
|
|
64
|
+
- **Local fixtures** — when no endpoint is configured (or the configured endpoint is unreachable), the server returns bundled demo fixtures so MCP clients always receive useful output.
|
|
65
|
+
|
|
66
|
+
## MCP client integration
|
|
67
|
+
|
|
68
|
+
The server speaks standard MCP over stdio, so it works with any MCP-capable client. Example configuration (Claude Desktop — `~/Library/Application Support/Claude/claude_desktop_config.json`; other clients use the same `mcpServers` shape):
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"mcpServers": {
|
|
73
|
+
"cleanlibrary": {
|
|
74
|
+
"command": "cleanlib-mcp-server"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
To connect a live CleanLibrary backend, add the endpoint + API key:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"mcpServers": {
|
|
85
|
+
"cleanlibrary": {
|
|
86
|
+
"command": "cleanlib-mcp-server",
|
|
87
|
+
"env": {
|
|
88
|
+
"CLEANLIB_ENDPOINT": "https://cleanapp.clnstrt.dev",
|
|
89
|
+
"CLEANLIB_API_KEY": "clk_..."
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The same `command` + `env` pattern applies to Cursor, GitHub Copilot, and other MCP clients — consult your client's MCP server configuration docs for the exact file location.
|
|
97
|
+
|
|
98
|
+
## Development
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
python -m venv .venv && source .venv/bin/activate
|
|
102
|
+
pip install -e ".[dev]"
|
|
103
|
+
ruff check src tests
|
|
104
|
+
pytest -v
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
Proprietary. See [LICENSE](./LICENSE) for terms. © 2026 CleanStart Inc.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
cleanlib_mcp/__init__.py,sha256=LEStHhF9QeN8MrazrjqIBmXVYptoRPjNAU8RuHp46Ec,265
|
|
2
|
+
cleanlib_mcp/backend.py,sha256=bcbVkHiyauesrh8hXl9hhwOXk_el_ER92XBz4RH08bw,8292
|
|
3
|
+
cleanlib_mcp/server.py,sha256=qh8-hvV3aE72VPRDExqUVRS5RnRN9L_PZM6WYnb6o2Q,5124
|
|
4
|
+
cleanlib_mcp_server-0.2.0.dist-info/licenses/LICENSE,sha256=-_ijBZ8qpgShm7v5vUkQOYYjiDnnjy1yVuqvNNxon6k,731
|
|
5
|
+
cleanlib_mcp_server-0.2.0.dist-info/METADATA,sha256=wFBRMhC64LsufNi4d6x6pTzG_Y5GplooDtAQtCxTc4Y,3932
|
|
6
|
+
cleanlib_mcp_server-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
7
|
+
cleanlib_mcp_server-0.2.0.dist-info/entry_points.txt,sha256=i6VvagI03Lf-MAWyR3p_Cgf093Hvj_I1XgdlWdcPmeQ,65
|
|
8
|
+
cleanlib_mcp_server-0.2.0.dist-info/top_level.txt,sha256=u8cHyffcPTv5xnmj7DPz-BSkorAHmNIJh8scb4o1ypk,13
|
|
9
|
+
cleanlib_mcp_server-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
CleanStart Inc Proprietary License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CleanStart Inc. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software is the proprietary property of CleanStart Inc. Use is permitted
|
|
6
|
+
only under the terms of a separate commercial license agreement with CleanStart
|
|
7
|
+
Inc. or its authorized distributors. Unauthorized use, modification, distribution,
|
|
8
|
+
or reverse engineering is prohibited.
|
|
9
|
+
|
|
10
|
+
For licensing inquiries: cto.office@cleanstart.com
|
|
11
|
+
|
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
13
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
14
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
15
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cleanlib_mcp
|