mastyf-ai 4.2.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.
- mastyf_ai-4.2.0/.gitignore +75 -0
- mastyf_ai-4.2.0/PKG-INFO +101 -0
- mastyf_ai-4.2.0/README.md +73 -0
- mastyf_ai-4.2.0/mastyf_ai/__init__.py +19 -0
- mastyf_ai-4.2.0/mastyf_ai/cli.py +60 -0
- mastyf_ai-4.2.0/mastyf_ai/client.py +130 -0
- mastyf_ai-4.2.0/mastyf_ai/middleware/__init__.py +1 -0
- mastyf_ai-4.2.0/mastyf_ai/middleware/langchain.py +45 -0
- mastyf_ai-4.2.0/mastyf_ai/middleware/openai_agents.py +33 -0
- mastyf_ai-4.2.0/mastyf_ai/scanner.py +33 -0
- mastyf_ai-4.2.0/pyproject.toml +42 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
node_modules/
|
|
2
|
+
build/
|
|
3
|
+
.envrc
|
|
4
|
+
*.tgz
|
|
5
|
+
package.json.prepack-backup
|
|
6
|
+
.packages.prepack-backup.json
|
|
7
|
+
.venv-charts/
|
|
8
|
+
dist/
|
|
9
|
+
*.db
|
|
10
|
+
.env
|
|
11
|
+
.DS_Store
|
|
12
|
+
*.log
|
|
13
|
+
package-lock.json
|
|
14
|
+
coverage/
|
|
15
|
+
test-results/
|
|
16
|
+
/reports/
|
|
17
|
+
docs/tutorials/videos/.promo-build/
|
|
18
|
+
docs/tutorials/videos/mastyf-ai-promo.mp4
|
|
19
|
+
apps/cloud/public/tutorials/mastyf-ai-promo.mp4
|
|
20
|
+
docs/tutorials/videos/*.mp4
|
|
21
|
+
apps/cloud/public/tutorials/*.mp4
|
|
22
|
+
.turbo/
|
|
23
|
+
.threat-state.json
|
|
24
|
+
.ai-learning.json
|
|
25
|
+
default-policy-auto-generated.yaml
|
|
26
|
+
scenarios/dogfood/output/
|
|
27
|
+
scenarios/dogfood/sandbox/
|
|
28
|
+
:memory:
|
|
29
|
+
test-report.html
|
|
30
|
+
test-report*.html
|
|
31
|
+
corpus-eval-report.json
|
|
32
|
+
/benchmark-report.json
|
|
33
|
+
assets/ml/prompt-injection/onnx/*.onnx
|
|
34
|
+
.venv/
|
|
35
|
+
.package.prepack-backup.json
|
|
36
|
+
scripts/.agent-proxy-config.json
|
|
37
|
+
# Runtime auto-threat-research fixtures beyond committed corpus (adv-425+)
|
|
38
|
+
/adversarial-harness/fixtures/custom-attacks/adv-42[5-9].json
|
|
39
|
+
/adversarial-harness/fixtures/custom-attacks/adv-43[0-9].json
|
|
40
|
+
/adversarial-harness/fixtures/custom-attacks/adv-[5-9][0-9][0-9].json
|
|
41
|
+
# Accidental tsc output in package src trees
|
|
42
|
+
packages/server/src/**/*.js
|
|
43
|
+
packages/server/src/**/*.d.ts
|
|
44
|
+
packages/server/src/**/*.js.map
|
|
45
|
+
packages/server/src/**/*.d.ts.map
|
|
46
|
+
scenarios/real-life/output/
|
|
47
|
+
reports/tenants/
|
|
48
|
+
reports/.mastyf-ai-home/
|
|
49
|
+
*.db-shm
|
|
50
|
+
*.db-wal
|
|
51
|
+
*.db.pid
|
|
52
|
+
reports/provenance-test.tar.gz
|
|
53
|
+
reports/insurance-test/
|
|
54
|
+
reports/home/tenants/default/dashboard-access.jsonl
|
|
55
|
+
.vercel
|
|
56
|
+
.env*.local
|
|
57
|
+
*.tsbuildinfo
|
|
58
|
+
apps/proxy-core/proxy-core
|
|
59
|
+
benchmarks/results/
|
|
60
|
+
sca/*.md
|
|
61
|
+
sca/*.png
|
|
62
|
+
sca/ai-learning-metrics.json
|
|
63
|
+
deploy/dashboard.html
|
|
64
|
+
deploy/dashboard-spa/out/
|
|
65
|
+
deploy/dashboard-spa/.next/
|
|
66
|
+
.next/
|
|
67
|
+
# Generated at runtime by scenarios/tests — must never be committed (leaks local absolute paths)
|
|
68
|
+
scenarios/real-life/proxy-filesystem-config.json
|
|
69
|
+
tests/e2e/test-config.json
|
|
70
|
+
tests/e2e/adversarial-test-config.json
|
|
71
|
+
# Orphaned demo/report exports should live under gitignored reports/, never under app/data
|
|
72
|
+
deploy/dashboard-spa/app/data/
|
|
73
|
+
# Runtime logs
|
|
74
|
+
data/tenants/default/logs/*.jsonl
|
|
75
|
+
mastyf-ai-docker.tar
|
mastyf_ai-4.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mastyf-ai
|
|
3
|
+
Version: 4.2.0
|
|
4
|
+
Summary: Mastyf AI — perimeter security proxy for MCP-based AI agents. Intercept, audit, and block dangerous tool calls.
|
|
5
|
+
Project-URL: Homepage, https://mastyf.ai
|
|
6
|
+
Project-URL: Repository, https://github.com/mastyf-ai/mastyf.ai
|
|
7
|
+
Project-URL: Documentation, https://docs.mastyf.ai
|
|
8
|
+
Author-email: Mastyf AI <dev@mastyf.ai>
|
|
9
|
+
License: AGPL-3.0
|
|
10
|
+
Keywords: ai,llm,mastyf,mcp,prompt-injection,proxy,security
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Security
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: httpx>=0.27.0
|
|
22
|
+
Requires-Dist: pydantic>=2.0.0
|
|
23
|
+
Provides-Extra: langchain
|
|
24
|
+
Requires-Dist: langchain-core>=0.3.0; extra == 'langchain'
|
|
25
|
+
Provides-Extra: openai-agents
|
|
26
|
+
Requires-Dist: openai>=1.0.0; extra == 'openai-agents'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# Mastyf AI — Python SDK
|
|
30
|
+
|
|
31
|
+
Perimeter security proxy for MCP-based AI agents. Evaluates every tool call through the Mastyf policy engine before execution.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install mastyf-ai
|
|
37
|
+
|
|
38
|
+
# With framework integrations:
|
|
39
|
+
pip install mastyf-ai[openai-agents] # OpenAI Agents SDK
|
|
40
|
+
pip install mastyf-ai[langchain] # LangChain
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from mastyf_ai import MastyfProxy
|
|
47
|
+
|
|
48
|
+
proxy = MastyfProxy("http://localhost:4000")
|
|
49
|
+
|
|
50
|
+
# Check health
|
|
51
|
+
print(proxy.health()) # {'status': 'ready'}
|
|
52
|
+
|
|
53
|
+
# Test a tool call against policy
|
|
54
|
+
decision = proxy.test_policy("read_file", {"path": "/etc/passwd"})
|
|
55
|
+
print(f"{decision.action}: {decision.reason}")
|
|
56
|
+
# Output: block: Path not in allowed list
|
|
57
|
+
|
|
58
|
+
# Scan a package
|
|
59
|
+
from mastyf_ai import TrustScanner
|
|
60
|
+
scanner = TrustScanner()
|
|
61
|
+
result = scanner.scan("@modelcontextprotocol/server-filesystem")
|
|
62
|
+
print(f"{result['trustGrade']} ({result['trustScore']}/100)")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Framework Integration
|
|
66
|
+
|
|
67
|
+
### OpenAI Agents SDK
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from mastyf_ai import MastyfOpenAIGuard
|
|
71
|
+
|
|
72
|
+
guard = MastyfOpenAIGuard("http://localhost:4000")
|
|
73
|
+
|
|
74
|
+
@guard.wrap_tool
|
|
75
|
+
async def read_file(path: str) -> str:
|
|
76
|
+
with open(path) as f:
|
|
77
|
+
return f.read()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### LangChain
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from mastyf_ai import MastyfLangChainGuard
|
|
84
|
+
|
|
85
|
+
guard = MastyfLangChainGuard()
|
|
86
|
+
safe_tool = guard.wrap_tool(my_langchain_tool)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## CLI
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
mastyf health
|
|
93
|
+
mastyf test read_file --args '{"path":"/etc/passwd"}'
|
|
94
|
+
mastyf scan @modelcontextprotocol/server-filesystem
|
|
95
|
+
mastyf hooks
|
|
96
|
+
mastyf audit
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## API Reference
|
|
100
|
+
|
|
101
|
+
See [docs.mastyf.ai](https://docs.mastyf.ai) for full API documentation.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Mastyf AI — Python SDK
|
|
2
|
+
|
|
3
|
+
Perimeter security proxy for MCP-based AI agents. Evaluates every tool call through the Mastyf policy engine before execution.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install mastyf-ai
|
|
9
|
+
|
|
10
|
+
# With framework integrations:
|
|
11
|
+
pip install mastyf-ai[openai-agents] # OpenAI Agents SDK
|
|
12
|
+
pip install mastyf-ai[langchain] # LangChain
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from mastyf_ai import MastyfProxy
|
|
19
|
+
|
|
20
|
+
proxy = MastyfProxy("http://localhost:4000")
|
|
21
|
+
|
|
22
|
+
# Check health
|
|
23
|
+
print(proxy.health()) # {'status': 'ready'}
|
|
24
|
+
|
|
25
|
+
# Test a tool call against policy
|
|
26
|
+
decision = proxy.test_policy("read_file", {"path": "/etc/passwd"})
|
|
27
|
+
print(f"{decision.action}: {decision.reason}")
|
|
28
|
+
# Output: block: Path not in allowed list
|
|
29
|
+
|
|
30
|
+
# Scan a package
|
|
31
|
+
from mastyf_ai import TrustScanner
|
|
32
|
+
scanner = TrustScanner()
|
|
33
|
+
result = scanner.scan("@modelcontextprotocol/server-filesystem")
|
|
34
|
+
print(f"{result['trustGrade']} ({result['trustScore']}/100)")
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Framework Integration
|
|
38
|
+
|
|
39
|
+
### OpenAI Agents SDK
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from mastyf_ai import MastyfOpenAIGuard
|
|
43
|
+
|
|
44
|
+
guard = MastyfOpenAIGuard("http://localhost:4000")
|
|
45
|
+
|
|
46
|
+
@guard.wrap_tool
|
|
47
|
+
async def read_file(path: str) -> str:
|
|
48
|
+
with open(path) as f:
|
|
49
|
+
return f.read()
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### LangChain
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from mastyf_ai import MastyfLangChainGuard
|
|
56
|
+
|
|
57
|
+
guard = MastyfLangChainGuard()
|
|
58
|
+
safe_tool = guard.wrap_tool(my_langchain_tool)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## CLI
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
mastyf health
|
|
65
|
+
mastyf test read_file --args '{"path":"/etc/passwd"}'
|
|
66
|
+
mastyf scan @modelcontextprotocol/server-filesystem
|
|
67
|
+
mastyf hooks
|
|
68
|
+
mastyf audit
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## API Reference
|
|
72
|
+
|
|
73
|
+
See [docs.mastyf.ai](https://docs.mastyf.ai) for full API documentation.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mastyf AI — Python SDK for MCP security proxy.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from mastyf_ai import MastyfProxy
|
|
6
|
+
|
|
7
|
+
proxy = MastyfProxy("http://localhost:4000")
|
|
8
|
+
proxy.start()
|
|
9
|
+
|
|
10
|
+
# The proxy is now protecting all MCP tool calls
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .client import MastyfProxy, MastyfConfig
|
|
14
|
+
from .middleware.openai_agents import MastyfOpenAIGuard
|
|
15
|
+
from .middleware.langchain import MastyfLangChainGuard
|
|
16
|
+
from .scanner import TrustScanner
|
|
17
|
+
|
|
18
|
+
__version__ = "4.2.0"
|
|
19
|
+
__all__ = ["MastyfProxy", "MastyfConfig", "MastyfOpenAIGuard", "MastyfLangChainGuard", "TrustScanner"]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Mastyf AI CLI — Python entry point."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from .client import MastyfProxy, MastyfConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
parser = argparse.ArgumentParser(description="Mastyf AI — MCP security proxy client")
|
|
12
|
+
parser.add_argument("--proxy-url", default="http://localhost:4000", help="Proxy URL")
|
|
13
|
+
parser.add_argument("--api-key", help="API key for authentication")
|
|
14
|
+
|
|
15
|
+
sub = parser.add_subparsers(dest="command")
|
|
16
|
+
|
|
17
|
+
health = sub.add_parser("health", help="Check proxy health")
|
|
18
|
+
test = sub.add_parser("test", help="Test a tool call against policy")
|
|
19
|
+
test.add_argument("tool", help="Tool name")
|
|
20
|
+
test.add_argument("--args", default="{}", help="JSON arguments")
|
|
21
|
+
scan = sub.add_parser("scan", help="Scan an MCP package for trust score")
|
|
22
|
+
scan.add_argument("package", help="Package name")
|
|
23
|
+
hooks = sub.add_parser("hooks", help="List registered hooks")
|
|
24
|
+
audit = sub.add_parser("audit", help="Verify audit chain")
|
|
25
|
+
|
|
26
|
+
args = parser.parse_args()
|
|
27
|
+
|
|
28
|
+
if not args.command:
|
|
29
|
+
parser.print_help()
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
proxy = MastyfProxy(MastyfConfig(proxy_url=args.proxy_url, api_key=args.api_key))
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
if args.command == "health":
|
|
36
|
+
print(json.dumps(proxy.health(), indent=2))
|
|
37
|
+
elif args.command == "test":
|
|
38
|
+
tool_args = json.loads(args.args)
|
|
39
|
+
decision = proxy.test_policy(args.tool, tool_args)
|
|
40
|
+
print(f"Action: {decision.action}\nRule: {decision.rule}\nReason: {decision.reason}")
|
|
41
|
+
elif args.command == "scan":
|
|
42
|
+
from .scanner import TrustScanner
|
|
43
|
+
scanner = TrustScanner(args.proxy_url)
|
|
44
|
+
result = scanner.scan(args.package)
|
|
45
|
+
print(f"Package: {result.get('packageName')}")
|
|
46
|
+
print(f"Score: {result.get('trustScore')}/100")
|
|
47
|
+
print(f"Grade: {result.get('trustGrade')}")
|
|
48
|
+
print(f"Dimensions: {json.dumps(result.get('dimensions', {}), indent=2)}")
|
|
49
|
+
elif args.command == "hooks":
|
|
50
|
+
for h in proxy.get_hooks():
|
|
51
|
+
print(f" {h['name']} ({h['type']}, enabled={h['enabled']})")
|
|
52
|
+
elif args.command == "audit":
|
|
53
|
+
chain = proxy.get_audit_chain()
|
|
54
|
+
print(f"Audit entries: {chain.get('entries', 0)}")
|
|
55
|
+
print(f"Integrity: {'OK' if chain.get('ok') else 'BROKEN (' + str(chain.get('breaks', 0)) + ' breaks)'}")
|
|
56
|
+
except Exception as e:
|
|
57
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
finally:
|
|
60
|
+
proxy.close()
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Mastyf proxy client — connects to a running Mastyf AI proxy instance."""
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Optional, Dict, Any, List
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class MastyfConfig:
|
|
10
|
+
proxy_url: str = "http://localhost:4000"
|
|
11
|
+
api_key: Optional[str] = None
|
|
12
|
+
tenant_id: str = "default"
|
|
13
|
+
timeout: float = 10.0
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class PolicyDecision:
|
|
18
|
+
action: str # "pass", "block", "flag"
|
|
19
|
+
rule: str
|
|
20
|
+
reason: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MastyfProxy:
|
|
24
|
+
"""Client for a running Mastyf AI proxy instance."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, config: MastyfConfig | str = "http://localhost:4000"):
|
|
27
|
+
if isinstance(config, str):
|
|
28
|
+
config = MastyfConfig(proxy_url=config)
|
|
29
|
+
self.config = config
|
|
30
|
+
self._client = httpx.Client(
|
|
31
|
+
base_url=config.proxy_url.rstrip("/"),
|
|
32
|
+
timeout=config.timeout,
|
|
33
|
+
headers=self._build_headers(),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def _build_headers(self) -> Dict[str, str]:
|
|
37
|
+
headers = {"Content-Type": "application/json"}
|
|
38
|
+
if self.config.api_key:
|
|
39
|
+
headers["Authorization"] = f"Bearer {self.config.api_key}"
|
|
40
|
+
headers["X-Tenant-Id"] = self.config.tenant_id
|
|
41
|
+
return headers
|
|
42
|
+
|
|
43
|
+
def health(self) -> Dict[str, Any]:
|
|
44
|
+
"""Check proxy health."""
|
|
45
|
+
r = self._client.get("/health")
|
|
46
|
+
r.raise_for_status()
|
|
47
|
+
return r.json()
|
|
48
|
+
|
|
49
|
+
def test_policy(self, tool: str, args: Dict[str, Any], server: str = "default") -> PolicyDecision:
|
|
50
|
+
"""Test a tool call against the current policy without executing it."""
|
|
51
|
+
r = self._client.post("/api/policy/test", json={
|
|
52
|
+
"tool": tool,
|
|
53
|
+
"server": server,
|
|
54
|
+
"args": args,
|
|
55
|
+
})
|
|
56
|
+
r.raise_for_status()
|
|
57
|
+
data = r.json()
|
|
58
|
+
return PolicyDecision(
|
|
59
|
+
action=data.get("action", "pass"),
|
|
60
|
+
rule=data.get("rule", "unknown"),
|
|
61
|
+
reason=data.get("reason", ""),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def evaluate_tool_call(self, tool: str, args: Dict[str, Any], server: str = "default") -> PolicyDecision:
|
|
65
|
+
"""Evaluate a tool call through the full policy engine and hooks."""
|
|
66
|
+
r = self._client.post(f"{self.config.proxy_url.rstrip('/')}/mcp", json={
|
|
67
|
+
"jsonrpc": "2.0",
|
|
68
|
+
"id": "python-sdk",
|
|
69
|
+
"method": "tools/call",
|
|
70
|
+
"params": {"name": tool, "arguments": args},
|
|
71
|
+
})
|
|
72
|
+
data = r.json()
|
|
73
|
+
if "error" in data:
|
|
74
|
+
err = data["error"]
|
|
75
|
+
return PolicyDecision(
|
|
76
|
+
action="block",
|
|
77
|
+
rule=err.get("data", {}).get("rule", "unknown"),
|
|
78
|
+
reason=err.get("message", ""),
|
|
79
|
+
)
|
|
80
|
+
return PolicyDecision(action="pass", rule="default", reason="")
|
|
81
|
+
|
|
82
|
+
def get_hooks(self) -> List[Dict[str, Any]]:
|
|
83
|
+
"""List all registered hooks."""
|
|
84
|
+
r = self._client.get("/api/hooks")
|
|
85
|
+
r.raise_for_status()
|
|
86
|
+
return r.json().get("hooks", [])
|
|
87
|
+
|
|
88
|
+
def register_hook(self, name: str, code: str, hook_type: str = "before", priority: int = 50) -> bool:
|
|
89
|
+
"""Register a custom hook."""
|
|
90
|
+
r = self._client.post("/api/hooks", json={
|
|
91
|
+
"name": name, "code": code, "type": hook_type, "priority": priority,
|
|
92
|
+
})
|
|
93
|
+
return r.status_code == 201
|
|
94
|
+
|
|
95
|
+
def store_credential(self, token: str, provider: str = "api", credential_type: str = "bearer_token") -> str:
|
|
96
|
+
"""Store an API token in the encrypted credential broker."""
|
|
97
|
+
r = self._client.post("/api/credentials", json={
|
|
98
|
+
"token": token,
|
|
99
|
+
"providerName": provider,
|
|
100
|
+
"providerId": provider,
|
|
101
|
+
"credentialType": credential_type,
|
|
102
|
+
})
|
|
103
|
+
r.raise_for_status()
|
|
104
|
+
return r.json().get("id", "")
|
|
105
|
+
|
|
106
|
+
def get_audit_chain(self) -> Dict[str, Any]:
|
|
107
|
+
"""Verify audit hash chain integrity."""
|
|
108
|
+
r = self._client.get("/api/audit/verify")
|
|
109
|
+
r.raise_for_status()
|
|
110
|
+
return r.json()
|
|
111
|
+
|
|
112
|
+
def get_learning_suggestions(self) -> List[Dict[str, Any]]:
|
|
113
|
+
"""Get whitelist suggestions from learning mode."""
|
|
114
|
+
r = self._client.get("/api/learning/suggestions")
|
|
115
|
+
r.raise_for_status()
|
|
116
|
+
return r.json().get("suggestions", [])
|
|
117
|
+
|
|
118
|
+
def close(self):
|
|
119
|
+
self._client.close()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def run_eval(proxy: MastyfProxy, payloads: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
123
|
+
"""Run evaluation payloads against the proxy policy."""
|
|
124
|
+
r = httpx.post(
|
|
125
|
+
f"{proxy.config.proxy_url.rstrip('/')}/api/eval/run",
|
|
126
|
+
json={"payloads": payloads},
|
|
127
|
+
timeout=30.0,
|
|
128
|
+
)
|
|
129
|
+
r.raise_for_status()
|
|
130
|
+
return r.json()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Mastyf AI middleware for Python agent frameworks."""
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""LangChain integration for Mastyf AI security proxy."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Dict, Any, Callable
|
|
4
|
+
from ..client import MastyfProxy
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MastyfLangChainGuard:
|
|
8
|
+
"""Middleware for LangChain that evaluates tool calls through Mastyf before execution."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, proxy_url: str = "http://localhost:4000", api_key: Optional[str] = None):
|
|
11
|
+
self._proxy = MastyfProxy(proxy_url)
|
|
12
|
+
|
|
13
|
+
def wrap_tool(self, tool: Any) -> Any:
|
|
14
|
+
"""Wrap a LangChain tool with Mastyf policy evaluation."""
|
|
15
|
+
tool_name = getattr(tool, "name", tool.__class__.__name__)
|
|
16
|
+
original_invoke = tool._run if hasattr(tool, "_run") else tool.invoke
|
|
17
|
+
|
|
18
|
+
def safe_invoke(*args: Any, **kwargs: Any) -> Any:
|
|
19
|
+
args_dict = kwargs or {}
|
|
20
|
+
if args and len(args) > 0 and isinstance(args[0], str):
|
|
21
|
+
args_dict = {"input": args[0]}
|
|
22
|
+
decision = self._proxy.test_policy(tool_name, args_dict)
|
|
23
|
+
if decision.action == "block":
|
|
24
|
+
raise PermissionError(f"Mastyf blocked '{tool_name}': {decision.reason}")
|
|
25
|
+
return original_invoke(*args, **kwargs)
|
|
26
|
+
|
|
27
|
+
if hasattr(tool, "_run"):
|
|
28
|
+
tool._run = safe_invoke
|
|
29
|
+
else:
|
|
30
|
+
tool.invoke = safe_invoke
|
|
31
|
+
return tool
|
|
32
|
+
|
|
33
|
+
def create_callback(self) -> Callable:
|
|
34
|
+
"""Create a callback for LangChain agent runs."""
|
|
35
|
+
def on_tool_start(tool_name: str, input_str: str) -> None:
|
|
36
|
+
import json
|
|
37
|
+
try:
|
|
38
|
+
args = json.loads(input_str)
|
|
39
|
+
except Exception:
|
|
40
|
+
args = {"input": input_str}
|
|
41
|
+
decision = self._proxy.test_policy(tool_name, args)
|
|
42
|
+
if decision.action == "block":
|
|
43
|
+
raise PermissionError(f"Mastyf blocked '{tool_name}': {decision.reason}")
|
|
44
|
+
|
|
45
|
+
return on_tool_start
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""OpenAI Agents SDK integration for Mastyf AI security proxy."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Dict, Any
|
|
4
|
+
from ..client import MastyfProxy, PolicyDecision
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MastyfOpenAIGuard:
|
|
8
|
+
"""Guard middleware for OpenAI Agents SDK that evaluates all tool calls through Mastyf."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, proxy_url: str = "http://localhost:4000", api_key: Optional[str] = None):
|
|
11
|
+
self._proxy = MastyfProxy(proxy_url) if isinstance(proxy_url, str) else proxy_url
|
|
12
|
+
self._proxy.config.api_key = api_key
|
|
13
|
+
|
|
14
|
+
async def before_tool_call(self, tool_name: str, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
15
|
+
"""Called before every tool execution. Returns {'allowed': bool, 'reason': str}."""
|
|
16
|
+
decision = self._proxy.test_policy(tool_name, args)
|
|
17
|
+
return {
|
|
18
|
+
"allowed": decision.action != "block",
|
|
19
|
+
"reason": decision.reason if decision.action == "block" else "",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def wrap_tool(self, tool_fn, tool_name: str):
|
|
23
|
+
"""Wrap a tool function with policy evaluation."""
|
|
24
|
+
original = tool_fn
|
|
25
|
+
|
|
26
|
+
async def wrapped(**kwargs):
|
|
27
|
+
decision = self._proxy.test_policy(tool_name, kwargs)
|
|
28
|
+
if decision.action == "block":
|
|
29
|
+
raise PermissionError(f"Mastyf blocked '{tool_name}': {decision.reason}")
|
|
30
|
+
return await original(**kwargs)
|
|
31
|
+
|
|
32
|
+
wrapped.__name__ = tool_name
|
|
33
|
+
return wrapped
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""MCP package trust scanner — scan npm packages for security grades."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Any, Optional
|
|
4
|
+
from ..client import MastyfProxy
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TrustScanner:
|
|
8
|
+
"""Scan MCP server packages for trust scores."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, proxy_url: str = "http://localhost:4000"):
|
|
11
|
+
self._proxy = MastyfProxy(proxy_url)
|
|
12
|
+
|
|
13
|
+
def scan(self, package_name: str) -> Dict[str, Any]:
|
|
14
|
+
"""Scan a package and return its trust score."""
|
|
15
|
+
import httpx
|
|
16
|
+
r = httpx.post(
|
|
17
|
+
f"{self._proxy.config.proxy_url.rstrip('/')}/api/registry/scan",
|
|
18
|
+
json={"packageName": package_name},
|
|
19
|
+
timeout=15.0,
|
|
20
|
+
)
|
|
21
|
+
r.raise_for_status()
|
|
22
|
+
data = r.json()
|
|
23
|
+
return data.get("result", {})
|
|
24
|
+
|
|
25
|
+
def score(self, package_name: str) -> Optional[int]:
|
|
26
|
+
"""Return just the numeric trust score (0-100)."""
|
|
27
|
+
result = self.scan(package_name)
|
|
28
|
+
return result.get("trustScore")
|
|
29
|
+
|
|
30
|
+
def grade(self, package_name: str) -> Optional[str]:
|
|
31
|
+
"""Return just the trust grade (A+ through F)."""
|
|
32
|
+
result = self.scan(package_name)
|
|
33
|
+
return result.get("trustGrade")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mastyf-ai"
|
|
7
|
+
version = "4.2.0"
|
|
8
|
+
description = "Mastyf AI — perimeter security proxy for MCP-based AI agents. Intercept, audit, and block dangerous tool calls."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "AGPL-3.0"}
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Mastyf AI", email = "dev@mastyf.ai"},
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: GNU Affero General Public License v3",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Security",
|
|
24
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"httpx>=0.27.0",
|
|
28
|
+
"pydantic>=2.0.0",
|
|
29
|
+
]
|
|
30
|
+
keywords = ["mcp", "security", "ai", "llm", "proxy", "prompt-injection", "mastyf"]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
openai-agents = ["openai>=1.0.0"]
|
|
34
|
+
langchain = ["langchain-core>=0.3.0"]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://mastyf.ai"
|
|
38
|
+
Repository = "https://github.com/mastyf-ai/mastyf.ai"
|
|
39
|
+
Documentation = "https://docs.mastyf.ai"
|
|
40
|
+
|
|
41
|
+
[project.scripts]
|
|
42
|
+
mastyf = "mastyf_ai.cli:main"
|