opentrust-sdk 1.0.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.
- opentrust/__init__.py +85 -0
- opentrust/_client.py +31 -0
- opentrust/_recommend.py +60 -0
- opentrust/_types.py +21 -0
- opentrust/mcp.py +69 -0
- opentrust_sdk-1.0.0.dist-info/METADATA +60 -0
- opentrust_sdk-1.0.0.dist-info/RECORD +10 -0
- opentrust_sdk-1.0.0.dist-info/WHEEL +5 -0
- opentrust_sdk-1.0.0.dist-info/entry_points.txt +2 -0
- opentrust_sdk-1.0.0.dist-info/top_level.txt +1 -0
opentrust/__init__.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""opentrust — Python SDK for the OpenTrust tool trust registry."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
|
|
6
|
+
from ._client import _Client
|
|
7
|
+
from ._recommend import TRUST_LEVELS, recommend, risk_level
|
|
8
|
+
from ._types import ToolsPage, VerifyResult
|
|
9
|
+
|
|
10
|
+
__version__ = "0.1.0"
|
|
11
|
+
__all__ = [
|
|
12
|
+
"verify", "get", "search", "list",
|
|
13
|
+
"verify_sync", "get_sync",
|
|
14
|
+
"VerifyResult", "ToolsPage",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _build_result(passport: dict) -> VerifyResult:
|
|
19
|
+
status = passport.get("trust_status", "auto_generated_draft")
|
|
20
|
+
level = TRUST_LEVELS.get(status, 1)
|
|
21
|
+
perms = passport.get("permission_manifest") or {}
|
|
22
|
+
return VerifyResult(
|
|
23
|
+
slug=passport["slug"],
|
|
24
|
+
trust_status=status,
|
|
25
|
+
trust_level=level,
|
|
26
|
+
is_disputed=(status == "disputed"),
|
|
27
|
+
recommendation=recommend(status, perms),
|
|
28
|
+
risk=risk_level(status, perms),
|
|
29
|
+
passport=passport,
|
|
30
|
+
permissions=perms,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def verify(slug: str, *, api_url: str | None = None) -> VerifyResult:
|
|
35
|
+
"""Fetch a passport and return a VerifyResult with recommendation and risk level."""
|
|
36
|
+
passport = await get(slug, api_url=api_url)
|
|
37
|
+
return _build_result(passport)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def get(slug: str, *, api_url: str | None = None) -> dict:
|
|
41
|
+
"""Fetch the raw passport dict for a slug."""
|
|
42
|
+
client = _Client(base_url=api_url)
|
|
43
|
+
return await client.get(f"/tools/{slug}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def search(
|
|
47
|
+
query: str,
|
|
48
|
+
*,
|
|
49
|
+
trust_status: str | None = None,
|
|
50
|
+
api_url: str | None = None,
|
|
51
|
+
) -> list[dict]:
|
|
52
|
+
"""Search tools by query. Returns list of raw passport dicts."""
|
|
53
|
+
client = _Client(base_url=api_url)
|
|
54
|
+
page = await client.get("/tools", q=query, trust_status=trust_status)
|
|
55
|
+
return page.get("items", [])
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def list( # noqa: A001
|
|
59
|
+
*,
|
|
60
|
+
page: int = 1,
|
|
61
|
+
limit: int = 20,
|
|
62
|
+
trust_status: str | None = None,
|
|
63
|
+
api_url: str | None = None,
|
|
64
|
+
) -> ToolsPage:
|
|
65
|
+
"""List tools with optional filters. Returns a ToolsPage."""
|
|
66
|
+
client = _Client(base_url=api_url)
|
|
67
|
+
data = await client.get(
|
|
68
|
+
"/tools", page=page, limit=limit, trust_status=trust_status
|
|
69
|
+
)
|
|
70
|
+
return ToolsPage(
|
|
71
|
+
items=data.get("items", []),
|
|
72
|
+
total=data.get("total", 0),
|
|
73
|
+
page=data.get("page", page),
|
|
74
|
+
limit=data.get("limit", limit),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def verify_sync(slug: str, *, api_url: str | None = None) -> VerifyResult:
|
|
79
|
+
"""Synchronous wrapper for verify(). Do not call from an async context."""
|
|
80
|
+
return asyncio.run(verify(slug, api_url=api_url))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_sync(slug: str, *, api_url: str | None = None) -> dict:
|
|
84
|
+
"""Synchronous wrapper for get(). Do not call from an async context."""
|
|
85
|
+
return asyncio.run(get(slug, api_url=api_url))
|
opentrust/_client.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Internal async HTTP client wrapping httpx."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
_DEFAULT_URL = "https://api-kappa-pied-59.vercel.app"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _Client:
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
base_url: str | None = None,
|
|
16
|
+
transport: httpx.AsyncBaseTransport | None = None,
|
|
17
|
+
) -> None:
|
|
18
|
+
self._base = (
|
|
19
|
+
base_url or os.getenv("OPENTRUST_API_URL") or _DEFAULT_URL
|
|
20
|
+
).rstrip("/")
|
|
21
|
+
self._transport = transport
|
|
22
|
+
|
|
23
|
+
async def get(self, path: str, **params: Any) -> Any:
|
|
24
|
+
"""GET /api/v1{path} with optional query params (None values are dropped)."""
|
|
25
|
+
filtered = {k: v for k, v in params.items() if v is not None}
|
|
26
|
+
async with httpx.AsyncClient(
|
|
27
|
+
base_url=self._base, transport=self._transport
|
|
28
|
+
) as client:
|
|
29
|
+
resp = await client.get(f"/api/v1{path}", params=filtered)
|
|
30
|
+
resp.raise_for_status()
|
|
31
|
+
return resp.json()
|
opentrust/_recommend.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Recommendation table and risk logic for OpenTrust trust statuses."""
|
|
2
|
+
|
|
3
|
+
TRUST_LEVELS: dict[str, int] = {
|
|
4
|
+
"auto_generated_draft": 1,
|
|
5
|
+
"creator_claimed": 2,
|
|
6
|
+
"seller_confirmed": 3,
|
|
7
|
+
"community_reviewed": 4,
|
|
8
|
+
"reviewer_signed": 5,
|
|
9
|
+
"security_checked": 6,
|
|
10
|
+
"continuously_monitored": 7,
|
|
11
|
+
"disputed": 0,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
_RECOMMENDATIONS: dict[int, str] = {
|
|
15
|
+
0: "⛔ Under active dispute. Do not use until resolved.",
|
|
16
|
+
1: "Auto-generated draft. Do not use in any agent workflow.",
|
|
17
|
+
2: "Creator claimed. Verify source independently before use.",
|
|
18
|
+
3: "Seller confirmed. Suitable for sandboxed/test environments only.",
|
|
19
|
+
4: "Community reviewed. Safe for low-risk tasks. Require level 6+ for production.",
|
|
20
|
+
5: "Reviewer signed. Suitable for most production tasks without sensitive permissions.",
|
|
21
|
+
6: "Security checked. Safe for production including sensitive permissions.",
|
|
22
|
+
7: "Continuously monitored. Highest trust level available.",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _perm_active(val: object) -> bool:
|
|
27
|
+
"""Return True if a permission value represents an active/granted permission."""
|
|
28
|
+
if val is True:
|
|
29
|
+
return True
|
|
30
|
+
if val and isinstance(val, dict):
|
|
31
|
+
return any(
|
|
32
|
+
v is True or (isinstance(v, list) and len(v) > 0)
|
|
33
|
+
for v in val.values()
|
|
34
|
+
)
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def recommend(trust_status: str, permission_manifest: dict) -> str:
|
|
39
|
+
"""Return a plain-English recommendation for a trust status + permission manifest."""
|
|
40
|
+
level = TRUST_LEVELS.get(trust_status, 1)
|
|
41
|
+
text = _RECOMMENDATIONS.get(level, _RECOMMENDATIONS[1])
|
|
42
|
+
if _perm_active(permission_manifest.get("wallet")):
|
|
43
|
+
text += " ⚠ Wallet access active — verify payment amounts before use."
|
|
44
|
+
if _perm_active(permission_manifest.get("terminal")):
|
|
45
|
+
text += " ⚠ Terminal access active — review allowed commands carefully."
|
|
46
|
+
return text
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def risk_level(trust_status: str, permission_manifest: dict) -> str:
|
|
50
|
+
"""Return 'low', 'medium', or 'high' risk for a trust status + permission manifest."""
|
|
51
|
+
if trust_status == "disputed":
|
|
52
|
+
return "high"
|
|
53
|
+
level = TRUST_LEVELS.get(trust_status, 1)
|
|
54
|
+
dangerous = {"wallet", "terminal", "private_data", "browser"}
|
|
55
|
+
n = sum(1 for k in dangerous if _perm_active(permission_manifest.get(k)))
|
|
56
|
+
if level <= 2 or n >= 2:
|
|
57
|
+
return "high"
|
|
58
|
+
if n == 1 or level <= 4:
|
|
59
|
+
return "medium"
|
|
60
|
+
return "low"
|
opentrust/_types.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class VerifyResult:
|
|
6
|
+
slug: str
|
|
7
|
+
trust_status: str # e.g. "community_reviewed"
|
|
8
|
+
trust_level: int # 1–7; 0 if trust_status == "disputed"
|
|
9
|
+
is_disputed: bool # True when trust_status == "disputed"
|
|
10
|
+
recommendation: str # plain-English guidance
|
|
11
|
+
risk: str # "low" | "medium" | "high"
|
|
12
|
+
passport: dict # full raw passport
|
|
13
|
+
permissions: dict # permission_manifest
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class ToolsPage:
|
|
18
|
+
items: list[dict]
|
|
19
|
+
total: int
|
|
20
|
+
page: int
|
|
21
|
+
limit: int
|
opentrust/mcp.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""OpenTrust MCP server — exposes trust registry as MCP tools via stdio."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from mcp.server.fastmcp import FastMCP
|
|
6
|
+
except ImportError as exc:
|
|
7
|
+
raise ImportError(
|
|
8
|
+
"MCP server requires the mcp extra: pip install opentrust-sdk[mcp]"
|
|
9
|
+
) from exc
|
|
10
|
+
|
|
11
|
+
import opentrust
|
|
12
|
+
|
|
13
|
+
mcp_server = FastMCP(
|
|
14
|
+
"OpenTrust",
|
|
15
|
+
instructions=(
|
|
16
|
+
"Query the OpenTrust tool trust registry. "
|
|
17
|
+
"Use verify_tool before calling any external tool to check its "
|
|
18
|
+
"trust status and permissions."
|
|
19
|
+
),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@mcp_server.tool()
|
|
24
|
+
async def verify_tool(slug: str) -> dict:
|
|
25
|
+
"""Look up a tool's trust passport and get a plain-English safety recommendation."""
|
|
26
|
+
result = await opentrust.verify(slug)
|
|
27
|
+
return {
|
|
28
|
+
"passport": result.passport,
|
|
29
|
+
"trust_status": result.trust_status,
|
|
30
|
+
"trust_level": result.trust_level,
|
|
31
|
+
"is_disputed": result.is_disputed,
|
|
32
|
+
"recommendation": result.recommendation,
|
|
33
|
+
"risk": result.risk,
|
|
34
|
+
"permissions": result.permissions,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@mcp_server.tool()
|
|
39
|
+
async def search_tools(query: str, trust_status: str = "") -> list:
|
|
40
|
+
"""Search the OpenTrust registry for tools matching a query."""
|
|
41
|
+
tools = await opentrust.search(query, trust_status=trust_status or None)
|
|
42
|
+
return [
|
|
43
|
+
{
|
|
44
|
+
"slug": t.get("slug"),
|
|
45
|
+
"name": t.get("name"),
|
|
46
|
+
"trust_status": t.get("trust_status"),
|
|
47
|
+
"description": t.get("description", ""),
|
|
48
|
+
}
|
|
49
|
+
for t in tools
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@mcp_server.tool()
|
|
54
|
+
async def list_tools(
|
|
55
|
+
page: int = 1, limit: int = 20, trust_status: str = ""
|
|
56
|
+
) -> dict:
|
|
57
|
+
"""List registered tools, optionally filtered by trust level."""
|
|
58
|
+
page_result = await opentrust.list(
|
|
59
|
+
page=page, limit=limit, trust_status=trust_status or None
|
|
60
|
+
)
|
|
61
|
+
return {"items": page_result.items, "total": page_result.total}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def main() -> None:
|
|
65
|
+
mcp_server.run()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
main()
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: opentrust-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python SDK and MCP bridge for the OpenTrust tool trust registry
|
|
5
|
+
Author-email: Novel Hut Studios <founder@novelhut.net>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Costder/opentrust
|
|
8
|
+
Project-URL: Repository, https://github.com/Costder/opentrust
|
|
9
|
+
Project-URL: Issues, https://github.com/Costder/opentrust/issues
|
|
10
|
+
Keywords: opentrust,ai-agents,mcp,trust-registry,tool-passports
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: httpx>=0.28
|
|
14
|
+
Provides-Extra: mcp
|
|
15
|
+
Requires-Dist: mcp>=1.0; extra == "mcp"
|
|
16
|
+
|
|
17
|
+
# opentrust-sdk
|
|
18
|
+
|
|
19
|
+
Python SDK for OpenTrust, the trust registry and passport layer for AI-agent tools.
|
|
20
|
+
|
|
21
|
+
Use it to verify tools, fetch passports, search the registry, and expose an MCP bridge for agent runtimes.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install opentrust-sdk
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
For MCP support:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install 'opentrust-sdk[mcp]'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Basic usage
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import asyncio
|
|
39
|
+
from opentrust import verify
|
|
40
|
+
|
|
41
|
+
async def main():
|
|
42
|
+
result = await verify('github/file-search-mcp')
|
|
43
|
+
print(result)
|
|
44
|
+
|
|
45
|
+
asyncio.run(main())
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## MCP bridge
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
opentrust-mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Repository
|
|
55
|
+
|
|
56
|
+
https://github.com/Costder/opentrust
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
opentrust/__init__.py,sha256=NfZEnxdjDqseH04JseV3adewUnWuUYxx50-ZWNCNn30,2661
|
|
2
|
+
opentrust/_client.py,sha256=rM51k36O_klQBTgrJU6SxMtoFjSK3Ur9yb1T0C-KVg8,970
|
|
3
|
+
opentrust/_recommend.py,sha256=_sjJ-UP1Fac1MhdyCN3SFuEzgk_AotmibfGxtUC7KHc,2405
|
|
4
|
+
opentrust/_types.py,sha256=M-lEUpJ4ed5CJ7jkuwq5M44VPRxUXUDmDF97tCXiEMo,576
|
|
5
|
+
opentrust/mcp.py,sha256=8s8DflH3Quz-tNd_k4II-SXj-Zk7BGeBwe_wxrDhXoM,1971
|
|
6
|
+
opentrust_sdk-1.0.0.dist-info/METADATA,sha256=aaBdr7w9BjbOk71rUDdEW_IUDIdmllfFM4SyQa7PS_Y,1233
|
|
7
|
+
opentrust_sdk-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
8
|
+
opentrust_sdk-1.0.0.dist-info/entry_points.txt,sha256=H0B1EE2BUp80fn0hPcIr5ii1EroYDOXF1NFhyHAaYVI,53
|
|
9
|
+
opentrust_sdk-1.0.0.dist-info/top_level.txt,sha256=IlL-VqxAmJkfN4-8bTA7IFP5WSWCUcrqLq1yK7mHOrE,10
|
|
10
|
+
opentrust_sdk-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
opentrust
|