@macaw-ai/client 0.0.0-stage → 0.9.11
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.
- package/LICENSE.md +339 -0
- package/bin/install.js +422 -0
- package/package.json +37 -4
- package/payload/README.md +56 -0
- package/payload/secureAI/LICENSE +190 -0
- package/payload/secureAI/NOTICE +5 -0
- package/payload/secureAI/demos/policy-controls/1_uncontrolled.py +139 -0
- package/payload/secureAI/demos/policy-controls/2_with_macaw.py +139 -0
- package/payload/secureAI/demos/policy-controls/3_with_user_policies.py +162 -0
- package/payload/secureAI/demos/policy-controls/credentials.txt +12 -0
- package/payload/secureAI/demos/policy-controls/malicious_report.txt +21 -0
- package/payload/secureAI/demos/policy-controls/sales_report.txt +20 -0
- package/payload/secureAI/demos/tutorial-1-rbac/README.md +143 -0
- package/payload/secureAI/demos/tutorial-1-rbac/app.py +147 -0
- package/payload/secureAI/demos/tutorial-1-rbac/authprompts_demo.py +306 -0
- package/payload/secureAI/demos/tutorial-1-rbac/config/claims-config.yaml +152 -0
- package/payload/secureAI/demos/tutorial-1-rbac/demo_secureopenai_with_policies.py +150 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/bu_Analytics.json +8 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/company_FinTech_Corp.json +70 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/llm-base.json +69 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/team_Reporting.json +8 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/user_alice.json +32 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/user_bob.json +28 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/user_carol.json +34 -0
- package/payload/secureAI/demos/tutorial-1-rbac/requirements.txt +6 -0
- package/payload/secureAI/demos/tutorial-1-rbac/setup/auth0_complete_setup.sh +392 -0
- package/payload/secureAI/demos/tutorial-1-rbac/setup/keycloak_complete_setup.sh +320 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/alation-remote-proxy.json +155 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/bu.json +22 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/company.json +17 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/user_alice.json +48 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/user_bob.json +83 -0
- package/payload/secureAI/demos/tutorial-2-intent/README.md +213 -0
- package/payload/secureAI/demos/tutorial-2-intent/script.py +188 -0
- package/payload/secureAI/demos/tutorial-2-intent/setup/alation_verifier.py +177 -0
- package/payload/secureAI/demos/tutorial-2-intent/setup/get_alation_user_token.py +180 -0
- package/payload/secureAI/examples/README.md +244 -0
- package/payload/secureAI/examples/anthropic/anthropic_1a_dropin_simple.py +103 -0
- package/payload/secureAI/examples/anthropic/anthropic_1b_multiuser_bind.py +190 -0
- package/payload/secureAI/examples/anthropic/anthropic_1b_multiuser_bind_streaming.py +190 -0
- package/payload/secureAI/examples/anthropic/anthropic_1c_a2a_invoke.py +156 -0
- package/payload/secureAI/examples/attestations/1a_trade_alice.py +215 -0
- package/payload/secureAI/examples/attestations/1a_trade_bob.py +164 -0
- package/payload/secureAI/examples/attestations/1b_grant_admin.py +170 -0
- package/payload/secureAI/examples/attestations/1b_grant_researcher.py +231 -0
- package/payload/secureAI/examples/attestations/README.md +128 -0
- package/payload/secureAI/examples/langchain/langchain_1a_dropin_simple.py +199 -0
- package/payload/secureAI/examples/langchain/langchain_1b_multiuser.py +262 -0
- package/payload/secureAI/examples/langchain/langchain_1c_orchestration.py +332 -0
- package/payload/secureAI/examples/langchain/langchain_1d_llm_openai.py +130 -0
- package/payload/secureAI/examples/langchain/langchain_1e_llm_anthropic.py +130 -0
- package/payload/secureAI/examples/langchain/langchain_1f_memory.py +214 -0
- package/payload/secureAI/examples/litellm/litellm_1a_dropin_simple.py +131 -0
- package/payload/secureAI/examples/mcp/1a_simple_invocation.py +115 -0
- package/payload/secureAI/examples/mcp/1b_discovery_and_resources.py +142 -0
- package/payload/secureAI/examples/mcp/1c_logging_client.py +100 -0
- package/payload/secureAI/examples/mcp/1d_progress_client.py +100 -0
- package/payload/secureAI/examples/mcp/1e_sampling_client.py +150 -0
- package/payload/secureAI/examples/mcp/1e_sampling_server.py +90 -0
- package/payload/secureAI/examples/mcp/1f_elicitation_client.py +127 -0
- package/payload/secureAI/examples/mcp/1f_elicitation_server.py +73 -0
- package/payload/secureAI/examples/mcp/1g_roots_client.py +122 -0
- package/payload/secureAI/examples/mcp/1g_roots_server.py +85 -0
- package/payload/secureAI/examples/mcp/2a_proxy_snowflake.py +212 -0
- package/payload/secureAI/examples/mcp/2b_proxy_databricks.py +299 -0
- package/payload/secureAI/examples/mcp/2c_proxy_stdio_fetch.py +91 -0
- package/payload/secureAI/examples/mcp/2d_proxy_stdio_salesforce.py +119 -0
- package/payload/secureAI/examples/mcp/2e_proxy_multiuser.py +136 -0
- package/payload/secureAI/examples/mcp/2f_proxy_stdio_databricks.py +144 -0
- package/payload/secureAI/examples/mcp/3a_native_mcp_client.py +104 -0
- package/payload/secureAI/examples/mcp/3b_native_mcp_proxy.py +114 -0
- package/payload/secureAI/examples/mcp/3c_native_mcp_sampling.py +180 -0
- package/payload/secureAI/examples/mcp/3d_native_mcp_2026.py +135 -0
- package/payload/secureAI/examples/mcp/securemcp_calculator.py +248 -0
- package/payload/secureAI/examples/mcp/securemcp_server.py +75 -0
- package/payload/secureAI/examples/openai/openai_1a_dropin_simple.py +102 -0
- package/payload/secureAI/examples/openai/openai_1b_multiuser_bind.py +193 -0
- package/payload/secureAI/examples/openai/openai_1b_multiuser_bind_streaming.py +199 -0
- package/payload/secureAI/examples/openai/openai_1c_a2a_invoke.py +156 -0
- package/payload/secureAI/examples/openai/openai_1d_invocation_patterns.py +337 -0
- package/payload/secureAI/examples/phishing-resistance/1a_login_alice.py +180 -0
- package/payload/secureAI/examples/phishing-resistance/1b_trade_alice.py +241 -0
- package/payload/secureAI/examples/phishing-resistance/1b_trade_bob.py +164 -0
- package/payload/secureAI/examples/phishing-resistance/README.md +124 -0
- package/payload/secureAI/examples/phishing-resistance/get_token.py +114 -0
- package/payload/secureAI/examples/pydantic_ai/README.md +88 -0
- package/payload/secureAI/examples/pydantic_ai/policies/app_pydantic-agent.json +10 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1a_dropin_simple.py +90 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1b_model_routing.py +85 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1c_native_tools.py +83 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1d_multiuser_bind.py +104 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1e_mcp_compose.py +92 -0
- package/payload/secureAI/macaw_adapters/__init__.py +55 -0
- package/payload/secureAI/macaw_adapters/anthropic/REFERENCE.md +374 -0
- package/payload/secureAI/macaw_adapters/anthropic/__init__.py +22 -0
- package/payload/secureAI/macaw_adapters/anthropic/secure_anthropic.py +792 -0
- package/payload/secureAI/macaw_adapters/anthropic_ext_tools/__init__.py +25 -0
- package/payload/secureAI/macaw_adapters/anthropic_ext_tools/secure_anthropic.py +840 -0
- package/payload/secureAI/macaw_adapters/langchain/REFERENCE.md +234 -0
- package/payload/secureAI/macaw_adapters/langchain/__init__.py +100 -0
- package/payload/secureAI/macaw_adapters/langchain/_utils.py +83 -0
- package/payload/secureAI/macaw_adapters/langchain/agents.py +296 -0
- package/payload/secureAI/macaw_adapters/langchain/anthropic.py +456 -0
- package/payload/secureAI/macaw_adapters/langchain/callbacks.py +306 -0
- package/payload/secureAI/macaw_adapters/langchain/memory.py +352 -0
- package/payload/secureAI/macaw_adapters/langchain/openai.py +410 -0
- package/payload/secureAI/macaw_adapters/langchain/tools.py +174 -0
- package/payload/secureAI/macaw_adapters/litellm/README.md +216 -0
- package/payload/secureAI/macaw_adapters/litellm/__init__.py +143 -0
- package/payload/secureAI/macaw_adapters/litellm/secure_litellm.py +686 -0
- package/payload/secureAI/macaw_adapters/mcp/REFERENCE.md +460 -0
- package/payload/secureAI/macaw_adapters/mcp/__init__.py +65 -0
- package/payload/secureAI/macaw_adapters/mcp/_endpoint.py +193 -0
- package/payload/secureAI/macaw_adapters/mcp/_endpoint2.py +225 -0
- package/payload/secureAI/macaw_adapters/mcp/_serve.py +25 -0
- package/payload/secureAI/macaw_adapters/mcp/client.py +721 -0
- package/payload/secureAI/macaw_adapters/mcp/decorators.py +228 -0
- package/payload/secureAI/macaw_adapters/mcp/fastmcp_compat.py +353 -0
- package/payload/secureAI/macaw_adapters/mcp/mcp.py +785 -0
- package/payload/secureAI/macaw_adapters/mcp/proxy.py +626 -0
- package/payload/secureAI/macaw_adapters/mcp/server.py +317 -0
- package/payload/secureAI/macaw_adapters/mcp/subscriptions.py +450 -0
- package/payload/secureAI/macaw_adapters/openai/REFERENCE.md +368 -0
- package/payload/secureAI/macaw_adapters/openai/__init__.py +22 -0
- package/payload/secureAI/macaw_adapters/openai/secure_openai.py +833 -0
- package/payload/secureAI/macaw_adapters/openai_ext_tools/REFERENCE.md +368 -0
- package/payload/secureAI/macaw_adapters/openai_ext_tools/__init__.py +25 -0
- package/payload/secureAI/macaw_adapters/openai_ext_tools/secure_openai.py +876 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/__init__.py +36 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/_core.py +257 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/agent.py +218 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/model.py +99 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/toolset.py +86 -0
- package/payload/secureAI/test_harness_quick.py +296 -0
- package/wheels/macaw_adapters-0.9.10.1-py3-none-any.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp312-cp312-macosx_11_0_arm64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp312-cp312-manylinux_2_17_x86_64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp314-cp314-macosx_11_0_arm64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp314-cp314-manylinux_2_17_x86_64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp314-cp314-win_amd64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp39-cp39-macosx_11_0_arm64.whl +0 -0
- package/README.md +0 -3
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
get_alation_user_token.py -- one-shot Alation USER token via OAuth authorization_code + PKCE.
|
|
4
|
+
|
|
5
|
+
Runs the interactive (User-Initiated) OAuth flow: opens a browser to Alation's authorize
|
|
6
|
+
endpoint, catches the redirect on a local callback server, and exchanges the code (+ PKCE
|
|
7
|
+
verifier) for an access_token + refresh_token that act AS the logged-in user. Prints an
|
|
8
|
+
`export ALATION_TOKEN=...` line you paste into the proxy env.
|
|
9
|
+
|
|
10
|
+
Prereqs: an Alation "User-Initiated OAuth Client" (Confidential, PKCE) whose Redirect URI is
|
|
11
|
+
EXACTLY the one below (default http://127.0.0.1:18722/callback).
|
|
12
|
+
|
|
13
|
+
Env / config:
|
|
14
|
+
ALATION_BASE_URL (required) e.g. https://macaw.mtse.alationcloud.com
|
|
15
|
+
ALATION_CLIENT_ID (required)
|
|
16
|
+
ALATION_CLIENT_SECRET (required for Confidential clients; omit for Public)
|
|
17
|
+
ALATION_REDIRECT_URI (default http://127.0.0.1:18722/callback -- MUST match the client)
|
|
18
|
+
ALATION_SCOPE (optional, space-delimited)
|
|
19
|
+
ALATION_AUTHORIZE_PATH (default /oauth/v1/authorize)
|
|
20
|
+
ALATION_TOKEN_PATH (default /oauth/v2/token/)
|
|
21
|
+
|
|
22
|
+
Run:
|
|
23
|
+
export ALATION_BASE_URL="https://macaw.mtse.alationcloud.com"
|
|
24
|
+
export ALATION_CLIENT_ID="..."
|
|
25
|
+
export ALATION_CLIENT_SECRET="..."
|
|
26
|
+
python get_alation_user_token.py # opens the browser, then prints the token
|
|
27
|
+
python get_alation_user_token.py --refresh <REFRESH_TOKEN> # renew without logging in again
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import base64
|
|
31
|
+
import hashlib
|
|
32
|
+
import http.server
|
|
33
|
+
import json
|
|
34
|
+
import os
|
|
35
|
+
import secrets
|
|
36
|
+
import sys
|
|
37
|
+
import threading
|
|
38
|
+
import urllib.error
|
|
39
|
+
import urllib.parse
|
|
40
|
+
import urllib.request
|
|
41
|
+
import webbrowser
|
|
42
|
+
|
|
43
|
+
BASE = os.environ.get("ALATION_BASE_URL", "").rstrip("/")
|
|
44
|
+
CLIENT_ID = os.environ.get("ALATION_CLIENT_ID", "")
|
|
45
|
+
CLIENT_SECRET = os.environ.get("ALATION_CLIENT_SECRET", "")
|
|
46
|
+
REDIRECT = os.environ.get("ALATION_REDIRECT_URI", "http://127.0.0.1:18722/callback")
|
|
47
|
+
SCOPE = os.environ.get("ALATION_SCOPE", "")
|
|
48
|
+
AUTHORIZE_PATH = os.environ.get("ALATION_AUTHORIZE_PATH", "/oauth/v1/authorize")
|
|
49
|
+
# authorization_code exchange is on the v1 family (v2/token is client_credentials-only,
|
|
50
|
+
# it returns unsupported_grant_type). Override with ALATION_TOKEN_PATH if your instance differs.
|
|
51
|
+
TOKEN_PATH = os.environ.get("ALATION_TOKEN_PATH", "/oauth/v1/token")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _post_token(data: dict) -> dict:
|
|
55
|
+
url = BASE + TOKEN_PATH
|
|
56
|
+
body = urllib.parse.urlencode(data).encode()
|
|
57
|
+
req = urllib.request.Request(
|
|
58
|
+
url, data=body,
|
|
59
|
+
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
60
|
+
)
|
|
61
|
+
try:
|
|
62
|
+
with urllib.request.urlopen(req) as r: # noqa: S310 (trusted Alation host)
|
|
63
|
+
return json.load(r)
|
|
64
|
+
except urllib.error.HTTPError as e:
|
|
65
|
+
detail = e.read().decode("utf-8", "replace")
|
|
66
|
+
sys.exit(
|
|
67
|
+
f"\nToken exchange FAILED: POST {url} -> HTTP {e.code}\n"
|
|
68
|
+
f" response: {detail}\n"
|
|
69
|
+
f" hint: if this says unsupported_grant_type / invalid endpoint, set "
|
|
70
|
+
f"ALATION_TOKEN_PATH=/oauth/v1/token (match the v1 authorize) and re-run "
|
|
71
|
+
f"(a NEW browser login -- the auth code is single-use)."
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _print_tokens(tok: dict) -> None:
|
|
76
|
+
at = tok.get("access_token")
|
|
77
|
+
rt = tok.get("refresh_token")
|
|
78
|
+
if not at:
|
|
79
|
+
sys.exit(f"No access_token in response: {tok}")
|
|
80
|
+
print("\n" + "=" * 64)
|
|
81
|
+
print("Alation USER access token (acts as the logged-in user):\n")
|
|
82
|
+
print(f'export ALATION_TOKEN="{at}"')
|
|
83
|
+
if rt:
|
|
84
|
+
print(f'\n# refresh token (renew later): {rt}')
|
|
85
|
+
print("=" * 64)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def refresh(refresh_token: str) -> None:
|
|
89
|
+
data = {"grant_type": "refresh_token", "refresh_token": refresh_token, "client_id": CLIENT_ID}
|
|
90
|
+
if CLIENT_SECRET:
|
|
91
|
+
data["client_secret"] = CLIENT_SECRET
|
|
92
|
+
_print_tokens(_post_token(data))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def login() -> None:
|
|
96
|
+
# PKCE
|
|
97
|
+
verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b"=").decode()
|
|
98
|
+
challenge = base64.urlsafe_b64encode(
|
|
99
|
+
hashlib.sha256(verifier.encode()).digest()
|
|
100
|
+
).rstrip(b"=").decode()
|
|
101
|
+
state = secrets.token_urlsafe(16)
|
|
102
|
+
|
|
103
|
+
q = {
|
|
104
|
+
"response_type": "code",
|
|
105
|
+
"client_id": CLIENT_ID,
|
|
106
|
+
"redirect_uri": REDIRECT,
|
|
107
|
+
"code_challenge": challenge,
|
|
108
|
+
"code_challenge_method": "S256",
|
|
109
|
+
"state": state,
|
|
110
|
+
}
|
|
111
|
+
if SCOPE:
|
|
112
|
+
q["scope"] = SCOPE
|
|
113
|
+
authorize_url = f"{BASE}{AUTHORIZE_PATH}?{urllib.parse.urlencode(q)}"
|
|
114
|
+
|
|
115
|
+
caught = {}
|
|
116
|
+
parsed = urllib.parse.urlparse(REDIRECT)
|
|
117
|
+
host, port, path = parsed.hostname, parsed.port or 80, parsed.path
|
|
118
|
+
|
|
119
|
+
class Handler(http.server.BaseHTTPRequestHandler):
|
|
120
|
+
def do_GET(self):
|
|
121
|
+
u = urllib.parse.urlparse(self.path)
|
|
122
|
+
if u.path != path:
|
|
123
|
+
self.send_response(404); self.end_headers(); return
|
|
124
|
+
params = urllib.parse.parse_qs(u.query)
|
|
125
|
+
caught["code"] = (params.get("code") or [None])[0]
|
|
126
|
+
caught["state"] = (params.get("state") or [None])[0]
|
|
127
|
+
caught["error"] = (params.get("error") or [None])[0]
|
|
128
|
+
self.send_response(200)
|
|
129
|
+
self.send_header("Content-Type", "text/html")
|
|
130
|
+
self.end_headers()
|
|
131
|
+
self.wfile.write(b"<h3>Alation login complete - you can close this tab.</h3>")
|
|
132
|
+
|
|
133
|
+
def log_message(self, *_): # silence
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
srv = http.server.HTTPServer((host, port), Handler)
|
|
137
|
+
threading.Thread(target=srv.handle_request, daemon=True).start()
|
|
138
|
+
|
|
139
|
+
print(f"Opening browser for Alation login...\n {authorize_url}\n", file=sys.stderr)
|
|
140
|
+
try:
|
|
141
|
+
webbrowser.open(authorize_url)
|
|
142
|
+
except Exception:
|
|
143
|
+
pass
|
|
144
|
+
print("If no browser opened, paste the URL above into one.", file=sys.stderr)
|
|
145
|
+
|
|
146
|
+
# wait for the single callback
|
|
147
|
+
import time
|
|
148
|
+
for _ in range(600): # ~5 min
|
|
149
|
+
if caught:
|
|
150
|
+
break
|
|
151
|
+
time.sleep(0.5)
|
|
152
|
+
if not caught:
|
|
153
|
+
sys.exit("Timed out waiting for the OAuth callback.")
|
|
154
|
+
if caught.get("error"):
|
|
155
|
+
sys.exit(f"Authorization error: {caught['error']}")
|
|
156
|
+
if caught.get("state") != state:
|
|
157
|
+
sys.exit("State mismatch (possible CSRF) -- aborting.")
|
|
158
|
+
code = caught.get("code")
|
|
159
|
+
if not code:
|
|
160
|
+
sys.exit("No authorization code received.")
|
|
161
|
+
|
|
162
|
+
data = {
|
|
163
|
+
"grant_type": "authorization_code",
|
|
164
|
+
"code": code,
|
|
165
|
+
"redirect_uri": REDIRECT,
|
|
166
|
+
"client_id": CLIENT_ID,
|
|
167
|
+
"code_verifier": verifier,
|
|
168
|
+
}
|
|
169
|
+
if CLIENT_SECRET:
|
|
170
|
+
data["client_secret"] = CLIENT_SECRET
|
|
171
|
+
_print_tokens(_post_token(data))
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
if __name__ == "__main__":
|
|
175
|
+
if not BASE or not CLIENT_ID:
|
|
176
|
+
sys.exit("Set ALATION_BASE_URL and ALATION_CLIENT_ID (see the module docstring).")
|
|
177
|
+
if len(sys.argv) == 3 and sys.argv[1] == "--refresh":
|
|
178
|
+
refresh(sys.argv[2])
|
|
179
|
+
else:
|
|
180
|
+
login()
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# MACAW Adapter Examples
|
|
2
|
+
|
|
3
|
+
These examples demonstrate how MACAW adapters add enterprise security controls to AI applications through **drop-in replacements** for popular frameworks.
|
|
4
|
+
|
|
5
|
+
## Three Classes of Adapters
|
|
6
|
+
|
|
7
|
+
MACAW provides API-compatible replacements across the AI stack:
|
|
8
|
+
|
|
9
|
+
| Category | Frameworks | What It Secures |
|
|
10
|
+
|----------|------------|-----------------|
|
|
11
|
+
| **LLM APIs** | OpenAI, Anthropic | Direct LLM calls (chat completions, messages) |
|
|
12
|
+
| **Agentic Frameworks** | MCP (Model Context Protocol) | Tool servers, resources, agent-to-agent calls |
|
|
13
|
+
| **Orchestrators** | LangChain | Agent executors, tool chains, multi-agent workflows |
|
|
14
|
+
|
|
15
|
+
The adapters are thin wrappers that route requests through MACAW's security layer. Change one import line - your existing code continues to work unchanged, now with enterprise security built in.
|
|
16
|
+
|
|
17
|
+
## Why MACAW?
|
|
18
|
+
|
|
19
|
+
Building production AI applications requires more than just API calls. You need:
|
|
20
|
+
|
|
21
|
+
- **Policy-based access controls** - Who can use which models? What token limits apply? Which tools are allowed?
|
|
22
|
+
- **Identity propagation** - User identity must flow through every layer for per-user policies
|
|
23
|
+
- **Audit trail** - Tamper-proof logs of every AI operation for compliance
|
|
24
|
+
- **Observability** - Trace requests through the AI stack layers
|
|
25
|
+
|
|
26
|
+
Without MACAW, implementing these controls requires months of custom development, careful security review, and ongoing maintenance across every integration point.
|
|
27
|
+
|
|
28
|
+
**With MACAW**, you get all of this by changing an import:
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
# Without MACAW: Alice can use any model, any tokens, no audit trail
|
|
32
|
+
from openai import OpenAI
|
|
33
|
+
client = OpenAI()
|
|
34
|
+
|
|
35
|
+
# With MACAW: Alice blocked from GPT-4 by policy, full audit trail
|
|
36
|
+
from macaw_adapters.openai import SecureOpenAI
|
|
37
|
+
client = SecureOpenAI(app_name="my-app")
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
MACAW deploys cryptographically-secured policy enforcement points at the edge using a zero-trust mesh architecture. Every tool endpoint becomes a PEP, enabling deterministic security controls even for non-deterministic AI systems.
|
|
41
|
+
|
|
42
|
+
## 30-Second Quick Start
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
# Install
|
|
46
|
+
# pip install macaw-adapters[openai]
|
|
47
|
+
|
|
48
|
+
from macaw_adapters.openai import SecureOpenAI
|
|
49
|
+
|
|
50
|
+
# Same API as OpenAI - just a different import
|
|
51
|
+
client = SecureOpenAI(app_name="my-app")
|
|
52
|
+
|
|
53
|
+
response = client.chat.completions.create(
|
|
54
|
+
model="gpt-4",
|
|
55
|
+
messages=[{"role": "user", "content": "Hello!"}]
|
|
56
|
+
)
|
|
57
|
+
print(response.choices[0].message.content)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
That's it. Your OpenAI calls now flow through MACAW's security layer with policy enforcement and audit logging.
|
|
61
|
+
|
|
62
|
+
## Examples by Framework
|
|
63
|
+
|
|
64
|
+
### OpenAI (`openai/`)
|
|
65
|
+
|
|
66
|
+
Drop-in replacement for the OpenAI Python SDK with full API compatibility including streaming.
|
|
67
|
+
|
|
68
|
+
| Example | Description |
|
|
69
|
+
|---------|-------------|
|
|
70
|
+
| `openai_1a_dropin_simple.py` | Basic drop-in replacement with app-level policy |
|
|
71
|
+
| `openai_1b_multiuser_bind.py` | Per-user policies via `bind_to_user()` pattern |
|
|
72
|
+
| `openai_1b_multiuser_bind_streaming.py` | Same as 1b with streaming responses |
|
|
73
|
+
| `openai_1c_a2a_invoke.py` | Agent-to-agent calls via `invoke_tool()` |
|
|
74
|
+
| `openai_1d_invocation_patterns.py` | Compares both invocation patterns side-by-side |
|
|
75
|
+
|
|
76
|
+
**Progression**: Start with 1a for single-user apps. Move to 1b when you need different permissions per user (SaaS). Use 1c for agent orchestration systems. See 1d to understand how both patterns achieve the same security guarantees.
|
|
77
|
+
|
|
78
|
+
### Anthropic (`anthropic/`)
|
|
79
|
+
|
|
80
|
+
Drop-in replacement for the Anthropic Python SDK with full API compatibility including streaming.
|
|
81
|
+
|
|
82
|
+
| Example | Description |
|
|
83
|
+
|---------|-------------|
|
|
84
|
+
| `anthropic_1a_dropin_simple.py` | Basic drop-in replacement with app-level policy |
|
|
85
|
+
| `anthropic_1b_multiuser_bind.py` | Per-user policies via `bind_to_user()` pattern |
|
|
86
|
+
| `anthropic_1b_multiuser_bind_streaming.py` | Same as 1b with streaming responses |
|
|
87
|
+
| `anthropic_1c_a2a_invoke.py` | Agent-to-agent calls via `invoke_tool()` |
|
|
88
|
+
|
|
89
|
+
**Progression**: Same pattern as OpenAI - the adapters provide consistent security semantics across LLM providers.
|
|
90
|
+
|
|
91
|
+
### MCP (`mcp/`)
|
|
92
|
+
|
|
93
|
+
Drop-in replacement for MCP (Model Context Protocol) servers and clients. Secures tool invocations, resource access, and agent communication.
|
|
94
|
+
|
|
95
|
+
| Example | Description |
|
|
96
|
+
|---------|-------------|
|
|
97
|
+
| `securemcp_server.py` | Basic SecureMCP server with tools and resources |
|
|
98
|
+
| `securemcp_calculator.py` | Calculator server with context tracking |
|
|
99
|
+
| `1a_simple_invocation.py` | Client invoking tools on a server |
|
|
100
|
+
| `1b_discovery_and_resources.py` | Discovering servers and accessing resources |
|
|
101
|
+
| `1c_logging_client.py` | Client with logging support |
|
|
102
|
+
| `1d_progress_client.py` | Progress tracking for long operations |
|
|
103
|
+
| `1e_sampling_*.py` | LLM sampling (client + server) |
|
|
104
|
+
| `1f_elicitation_*.py` | User input elicitation (client + server) |
|
|
105
|
+
| `1g_roots_*.py` | Filesystem roots (client + server) |
|
|
106
|
+
|
|
107
|
+
**Progression**: Start with the server example to create a SecureMCP tool server. Use 1a-1b to understand client patterns. Examples 1c-1g demonstrate advanced MCP features (logging, progress, sampling, elicitation, roots) all flowing through MACAW's security layer.
|
|
108
|
+
|
|
109
|
+
### LangChain (`langchain/`)
|
|
110
|
+
|
|
111
|
+
Drop-in replacements for LangChain agent components with MACAW security controls on tool access.
|
|
112
|
+
|
|
113
|
+
| Example | Description |
|
|
114
|
+
|---------|-------------|
|
|
115
|
+
| `langchain_1a_dropin_simple.py` | Basic agent with security policy |
|
|
116
|
+
| `langchain_1b_multiuser.py` | Per-user agents with different tool permissions |
|
|
117
|
+
| `langchain_1c_orchestration.py` | Multi-agent supervisor pattern |
|
|
118
|
+
| `langchain_1d_llm_openai.py` | Securing LLM calls (OpenAI) |
|
|
119
|
+
| `langchain_1e_llm_anthropic.py` | Securing LLM calls (Anthropic) |
|
|
120
|
+
| `langchain_1f_memory.py` | Memory integration with security |
|
|
121
|
+
|
|
122
|
+
**Progression**: Start with 1a to add tool access control to a single agent. Use 1b when different users need different tool permissions. Use 1c for multi-agent systems where each agent has isolated permissions.
|
|
123
|
+
|
|
124
|
+
### External Attestations (`attestations/`)
|
|
125
|
+
|
|
126
|
+
Human-in-the-loop approval workflows for sensitive operations. Request blocks until a manager/admin approves.
|
|
127
|
+
|
|
128
|
+
| Example | Description |
|
|
129
|
+
|---------|-------------|
|
|
130
|
+
| `1a_trade_alice.py` | Requester: High-value trade blocks for manager approval |
|
|
131
|
+
| `1a_trade_bob.py` | Approver: Manager approves/denies pending requests |
|
|
132
|
+
| `1b_grant_researcher.py` | Requester: First call blocks, subsequent calls instant |
|
|
133
|
+
| `1b_grant_admin.py` | Approver: Admin grants reusable capability |
|
|
134
|
+
|
|
135
|
+
**Progression**: Start with 1a to understand one-time approvals (each trade needs fresh approval). Use 1b for capability grants (approve once, use many times within TTL).
|
|
136
|
+
|
|
137
|
+
**Two-terminal pattern**: Run the requester script first, then run the approver script in another terminal while the requester waits.
|
|
138
|
+
|
|
139
|
+
### Phishing-Resistance (`phishing-resistance/`)
|
|
140
|
+
|
|
141
|
+
Login-strength gating. A tool runs only if the human logged in with a phishing-resistant method (passkey / hardware key). Decided by the control plane at login from the token's `amr` — no human in the loop.
|
|
142
|
+
|
|
143
|
+
| Example | Description |
|
|
144
|
+
|---------|-------------|
|
|
145
|
+
| `1a_login_alice.py` | Alice alone: a `phishing_resistant`-gated tool. Passkey → allowed, password → denied |
|
|
146
|
+
| `1b_trade_alice.py` | Requester: layers `phishing_resistant` in front of the trade approval |
|
|
147
|
+
| `1b_trade_bob.py` | Approver: manager approves the trade (once the phishing gate is cleared) |
|
|
148
|
+
| `get_token.py` | Signs in to Entra and mints an id_token carrying `amr` (passkey vs password) |
|
|
149
|
+
|
|
150
|
+
**Progression**: Start with 1a for the pure concept (run it twice — passkey vs password). Use 1b to see it composed as a precondition to a human approval (builds on `attestations/`).
|
|
151
|
+
|
|
152
|
+
## Running the Examples
|
|
153
|
+
|
|
154
|
+
### Prerequisites
|
|
155
|
+
|
|
156
|
+
1. **Install the adapters**:
|
|
157
|
+
```bash
|
|
158
|
+
pip install macaw-adapters[all]
|
|
159
|
+
# Or specific: pip install macaw-adapters[openai,anthropic,langchain,mcp]
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
2. **Set API keys** (for LLM examples):
|
|
163
|
+
```bash
|
|
164
|
+
export OPENAI_API_KEY=sk-...
|
|
165
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
3. **MACAW client configured** (`~/.macaw/config.json`)
|
|
169
|
+
|
|
170
|
+
### Running
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
# OpenAI drop-in example
|
|
174
|
+
python openai/openai_1a_dropin_simple.py
|
|
175
|
+
|
|
176
|
+
# Anthropic with streaming
|
|
177
|
+
python anthropic/anthropic_1b_multiuser_bind_streaming.py
|
|
178
|
+
|
|
179
|
+
# MCP server (run first)
|
|
180
|
+
python mcp/securemcp_server.py
|
|
181
|
+
# Then in another terminal:
|
|
182
|
+
python mcp/1a_simple_invocation.py
|
|
183
|
+
|
|
184
|
+
# LangChain multi-agent
|
|
185
|
+
python langchain/langchain_1c_orchestration.py
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Tutorial
|
|
189
|
+
|
|
190
|
+
For a complete end-to-end tutorial with policy setup, identity provider configuration, and a realistic multi-user scenario, see:
|
|
191
|
+
|
|
192
|
+
**[demos/tutorial-1/](../demos/tutorial-1/)** - Role-Based AI Access Control
|
|
193
|
+
|
|
194
|
+
This tutorial demonstrates a FinTech Corp scenario with hierarchical policies:
|
|
195
|
+
- **alice** (Analyst): GPT-3.5 only, 500 token limit
|
|
196
|
+
- **bob** (Manager): GPT-3.5/4, 2000 token limit
|
|
197
|
+
- **carol** (Admin): All models, 4000 token limit
|
|
198
|
+
|
|
199
|
+
Includes setup scripts for Keycloak and Auth0 identity providers, complete MAPL policy files, and three demo applications showing different integration patterns.
|
|
200
|
+
|
|
201
|
+
## Console Dev Hub
|
|
202
|
+
|
|
203
|
+
Everything in this repository is also available in the MACAW Console's Dev Hub with interactive features:
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
Console > Dev Hub
|
|
207
|
+
├── Quick Start
|
|
208
|
+
│ └── Download Client SDK (macOS/Linux/Windows, Python 3.9-3.12) and Adapters
|
|
209
|
+
├── Tutorials
|
|
210
|
+
│ └── Role-Based Access Control
|
|
211
|
+
│ ├── Multi-User SaaS Patterns
|
|
212
|
+
│ ├── Agent Orchestration
|
|
213
|
+
│ └── Policy Hierarchies
|
|
214
|
+
├── Examples
|
|
215
|
+
│ ├── OpenAI (drop-in, multi-user, streaming, A2A)
|
|
216
|
+
│ ├── Anthropic (drop-in, multi-user, streaming, A2A)
|
|
217
|
+
│ ├── MCP
|
|
218
|
+
│ │ ├── Simple Invocation
|
|
219
|
+
│ │ ├── Discovery & Resources
|
|
220
|
+
│ │ ├── Logging
|
|
221
|
+
│ │ ├── Progress Tracking
|
|
222
|
+
│ │ ├── Sampling
|
|
223
|
+
│ │ ├── Elicitation
|
|
224
|
+
│ │ └── Roots
|
|
225
|
+
│ └── LangChain
|
|
226
|
+
│ ├── Drop-in Agents
|
|
227
|
+
│ ├── Multi-user Permissions
|
|
228
|
+
│ ├── Agent Orchestration
|
|
229
|
+
│ ├── LLM Wrappers (OpenAI, Anthropic)
|
|
230
|
+
│ └── Memory Integration
|
|
231
|
+
└── Reference
|
|
232
|
+
├── MACAW Client SDK
|
|
233
|
+
├── Adapter APIs
|
|
234
|
+
├── MAPL Policy Language
|
|
235
|
+
└── Claims Mapping
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Access at [console.macawsecurity.ai](https://console.macawsecurity.ai) → Dev Hub tab.
|
|
239
|
+
|
|
240
|
+
## Learn More
|
|
241
|
+
|
|
242
|
+
- **Documentation**: [docs.macawsecurity.ai](https://docs.macawsecurity.ai)
|
|
243
|
+
- **Console**: [console.macawsecurity.ai](https://console.macawsecurity.ai)
|
|
244
|
+
- **GitHub**: [github.com/macawsecurity/secureAI](https://github.com/macawsecurity/secureAI)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
anthropic_1a_dropin_simple.py - Drop-in replacement for Anthropic client
|
|
4
|
+
|
|
5
|
+
The simplest integration path - just replace your import and it works.
|
|
6
|
+
No Identity Provider needed.
|
|
7
|
+
|
|
8
|
+
Prerequisites:
|
|
9
|
+
- MACAW SDK installed (pip install macaw-client macaw-adapters)
|
|
10
|
+
- ANTHROPIC_API_KEY environment variable
|
|
11
|
+
|
|
12
|
+
Run:
|
|
13
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
14
|
+
python anthropic_1a_dropin_simple.py
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
# BEFORE: from anthropic import Anthropic
|
|
21
|
+
# AFTER:
|
|
22
|
+
from macaw_adapters.anthropic import SecureAnthropic
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def main():
|
|
26
|
+
# Check for API key
|
|
27
|
+
if not os.environ.get("ANTHROPIC_API_KEY"):
|
|
28
|
+
print("Set ANTHROPIC_API_KEY environment variable")
|
|
29
|
+
print(" export ANTHROPIC_API_KEY=sk-ant-...")
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
print("=" * 60)
|
|
33
|
+
print("Example 1a: Drop-in Replacement (Anthropic)")
|
|
34
|
+
print("=" * 60)
|
|
35
|
+
print("\nPath: Direct on service (simplest)")
|
|
36
|
+
print("Use when: Single app, no user distinction, app-level policies")
|
|
37
|
+
|
|
38
|
+
# Create SecureAnthropic - just like regular Anthropic client
|
|
39
|
+
# Add intent_policy to restrict to Haiku only (no Sonnet/Opus)
|
|
40
|
+
client = SecureAnthropic(
|
|
41
|
+
app_name="my-simple-app",
|
|
42
|
+
intent_policy={
|
|
43
|
+
"constraints": {
|
|
44
|
+
"parameters": {
|
|
45
|
+
"tool:*/generate": {
|
|
46
|
+
"model": ["claude-3-haiku-20240307"], # Only Haiku allowed
|
|
47
|
+
"max_tokens": {"max": 200}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
print(f"\nService registered: {client.server_id}")
|
|
55
|
+
print(f"Mode: {client._mode}")
|
|
56
|
+
print("Policy: Haiku only, max 200 tokens")
|
|
57
|
+
|
|
58
|
+
# Same API as Anthropic - no changes needed
|
|
59
|
+
print("\n--- Test 1: Allowed model (claude-3-haiku) ---")
|
|
60
|
+
response = client.messages.create(
|
|
61
|
+
model="claude-3-haiku-20240307",
|
|
62
|
+
max_tokens=100,
|
|
63
|
+
messages=[
|
|
64
|
+
{"role": "user", "content": "What is compound interest? Answer briefly."}
|
|
65
|
+
]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
print(f"Model: {response.model}")
|
|
69
|
+
print(f"Response: {response.content[0].text}")
|
|
70
|
+
|
|
71
|
+
# Policy enforcement - try a blocked model
|
|
72
|
+
print("\n--- Test 2: Blocked model (claude-4.5-opus) ---")
|
|
73
|
+
try:
|
|
74
|
+
response = client.messages.create(
|
|
75
|
+
model="claude-opus-4-5-20251101", # NOT in allowed list!
|
|
76
|
+
max_tokens=100,
|
|
77
|
+
messages=[
|
|
78
|
+
{"role": "user", "content": "What is compound interest?"}
|
|
79
|
+
]
|
|
80
|
+
)
|
|
81
|
+
print(f"Unexpected success - should have been blocked!")
|
|
82
|
+
print(f"Response: {response.content[0].text[:100]}...")
|
|
83
|
+
except Exception as e:
|
|
84
|
+
print(f"Correctly blocked: {e}")
|
|
85
|
+
|
|
86
|
+
print("\n" + "=" * 60)
|
|
87
|
+
print("Example complete!")
|
|
88
|
+
print("=" * 60)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
try:
|
|
93
|
+
main()
|
|
94
|
+
except Exception as e:
|
|
95
|
+
err = str(e)
|
|
96
|
+
print("\n" + "=" * 60)
|
|
97
|
+
if "ANTHROPIC_API_KEY" in err or "api_key" in err.lower():
|
|
98
|
+
print("ERROR: Anthropic API key not configured")
|
|
99
|
+
print("Fix: export ANTHROPIC_API_KEY=sk-ant-...")
|
|
100
|
+
else:
|
|
101
|
+
print(f"ERROR: {e}")
|
|
102
|
+
print("=" * 60)
|
|
103
|
+
sys.exit(1)
|