@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,190 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
anthropic_1b_multiuser_bind.py - Multi-user Anthropic with bind_to_user
|
|
4
|
+
|
|
5
|
+
Each user's JWT identity flows through for policy evaluation.
|
|
6
|
+
Different users get different permissions based on their policies.
|
|
7
|
+
|
|
8
|
+
Prerequisites:
|
|
9
|
+
- MACAW SDK installed (pip install macaw-client macaw-adapters)
|
|
10
|
+
- ANTHROPIC_API_KEY environment variable
|
|
11
|
+
- Identity Provider configured (Console -> Settings -> Identity Bridge)
|
|
12
|
+
- Test users: alice/Alice123!, bob/Bob@123!
|
|
13
|
+
|
|
14
|
+
Run:
|
|
15
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
16
|
+
python anthropic_1b_multiuser_bind.py
|
|
17
|
+
|
|
18
|
+
No IdP configured? Run simpler example first:
|
|
19
|
+
python anthropic_1a_dropin_simple.py
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
|
|
25
|
+
from macaw_adapters.anthropic import SecureAnthropic
|
|
26
|
+
from macaw_client import MACAWClient, RemoteIdentityProvider
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Test configurations based on user policies
|
|
30
|
+
# Alice: Haiku only, max 500 tokens
|
|
31
|
+
# Bob: Haiku/Sonnet/Opus, max 2000 tokens
|
|
32
|
+
USER_TESTS = {
|
|
33
|
+
"alice": {
|
|
34
|
+
"password": "Alice123!",
|
|
35
|
+
"policy_desc": "Claude Haiku only, max 500 tokens",
|
|
36
|
+
"tests": [
|
|
37
|
+
# (model, max_tokens, should_succeed)
|
|
38
|
+
("claude-3-haiku-20240307", 400, True), # ALLOWED - correct model, within limit
|
|
39
|
+
("claude-opus-4-5-20251101", 400, False), # BLOCKED - alice can't use Opus
|
|
40
|
+
("claude-3-haiku-20240307", 600, False), # BLOCKED - exceeds max_tokens
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"bob": {
|
|
44
|
+
"password": "Bob@123!",
|
|
45
|
+
"policy_desc": "Claude Haiku/Sonnet/Opus, max 2000 tokens",
|
|
46
|
+
"tests": [
|
|
47
|
+
("claude-3-haiku-20240307", 400, True), # ALLOWED
|
|
48
|
+
("claude-opus-4-5-20251101", 400, True), # ALLOWED - bob CAN use Opus
|
|
49
|
+
("claude-3-haiku-20240307", 600, True), # ALLOWED - within bob's 2000 limit
|
|
50
|
+
("claude-opus-4-5-20251101", 2500, False), # BLOCKED - exceeds max_tokens
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def create_user_client(username: str, password: str) -> MACAWClient:
|
|
57
|
+
"""Create authenticated user client."""
|
|
58
|
+
print(f" Authenticating {username}...")
|
|
59
|
+
jwt_token, _ = RemoteIdentityProvider().login(username, password)
|
|
60
|
+
|
|
61
|
+
user = MACAWClient(
|
|
62
|
+
user_name=username,
|
|
63
|
+
iam_token=jwt_token,
|
|
64
|
+
agent_type="user",
|
|
65
|
+
app_name="financial-app"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if not user.register():
|
|
69
|
+
raise RuntimeError(f"Failed to register user {username}")
|
|
70
|
+
|
|
71
|
+
print(f" User agent: {user.agent_id}")
|
|
72
|
+
return user
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_user(username: str, anthropic_service: SecureAnthropic):
|
|
76
|
+
"""Test a user with bind_to_user pattern."""
|
|
77
|
+
config = USER_TESTS[username]
|
|
78
|
+
|
|
79
|
+
print(f"\n{'=' * 60}")
|
|
80
|
+
print(f"Testing {username.upper()} via bind_to_user")
|
|
81
|
+
print(f"Policy: {config['policy_desc']}")
|
|
82
|
+
print("=" * 60)
|
|
83
|
+
|
|
84
|
+
# 1. Create user client with JWT
|
|
85
|
+
user_client = create_user_client(username, config["password"])
|
|
86
|
+
|
|
87
|
+
# 2. Bind user to service
|
|
88
|
+
print(f"\n Binding to service: {anthropic_service.server_id}")
|
|
89
|
+
user_anthropic = anthropic_service.bind_to_user(user_client)
|
|
90
|
+
|
|
91
|
+
# 3. Test with different parameters
|
|
92
|
+
print(f"\n Running tests:")
|
|
93
|
+
for model, max_tokens, should_succeed in config["tests"]:
|
|
94
|
+
expected = "SHOULD SUCCEED" if should_succeed else "SHOULD BE BLOCKED"
|
|
95
|
+
print(f"\n -> {model}, {max_tokens} tokens ({expected})")
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
response = user_anthropic.messages.create(
|
|
99
|
+
model=model,
|
|
100
|
+
max_tokens=max_tokens,
|
|
101
|
+
messages=[
|
|
102
|
+
{"role": "user", "content": "What is revenue growth? Brief answer."}
|
|
103
|
+
]
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if should_succeed:
|
|
107
|
+
print(f" SUCCESS (as expected)")
|
|
108
|
+
print(f" Model: {response.model}")
|
|
109
|
+
print(f" Response: {response.content[0].text[:60]}...")
|
|
110
|
+
else:
|
|
111
|
+
print(f" UNEXPECTED SUCCESS - should have been blocked!")
|
|
112
|
+
|
|
113
|
+
except Exception as e:
|
|
114
|
+
error = str(e)
|
|
115
|
+
is_policy_block = any(word in error.lower() for word in
|
|
116
|
+
["not in allowed", "not permitted", "policy",
|
|
117
|
+
"max_tokens", "model", "blocked", "denied"])
|
|
118
|
+
if is_policy_block:
|
|
119
|
+
if should_succeed:
|
|
120
|
+
print(f" UNEXPECTED BLOCK: {error[:60]}...")
|
|
121
|
+
else:
|
|
122
|
+
print(f" CORRECTLY BLOCKED by policy")
|
|
123
|
+
else:
|
|
124
|
+
print(f" ERROR: {error[:60]}...")
|
|
125
|
+
|
|
126
|
+
# 4. Cleanup
|
|
127
|
+
print(f"\n Unbinding user...")
|
|
128
|
+
user_anthropic.unbind()
|
|
129
|
+
print(f" is_bound: {user_anthropic.is_bound}")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main():
|
|
133
|
+
# Check for API key
|
|
134
|
+
if not os.environ.get("ANTHROPIC_API_KEY"):
|
|
135
|
+
print("Set ANTHROPIC_API_KEY environment variable")
|
|
136
|
+
print(" export ANTHROPIC_API_KEY=sk-ant-...")
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
print("=" * 60)
|
|
140
|
+
print("Example 1b: Multi-user with bind_to_user (Anthropic)")
|
|
141
|
+
print("=" * 60)
|
|
142
|
+
print("\nPath: bind_to_user (per-user identity)")
|
|
143
|
+
print("Use when: SaaS app, different users need different permissions")
|
|
144
|
+
print("\nArchitecture:")
|
|
145
|
+
print(" - Single SecureAnthropic service (shared)")
|
|
146
|
+
print(" - Per-user MACAWClient with JWT identity")
|
|
147
|
+
print(" - bind_to_user() connects user to service")
|
|
148
|
+
print(" - User's identity flows through for policy evaluation")
|
|
149
|
+
|
|
150
|
+
# Create SINGLE service (shared across all users)
|
|
151
|
+
print("\n--- Creating SecureAnthropic service ---")
|
|
152
|
+
anthropic_service = SecureAnthropic(app_name="anthropic-service")
|
|
153
|
+
print(f"Service registered: {anthropic_service.server_id}")
|
|
154
|
+
print(f"Mode: {anthropic_service._mode}")
|
|
155
|
+
|
|
156
|
+
# Test alice (restricted)
|
|
157
|
+
try:
|
|
158
|
+
test_user("alice", anthropic_service)
|
|
159
|
+
except Exception as e:
|
|
160
|
+
print(f"\nFailed to test alice: {e}")
|
|
161
|
+
|
|
162
|
+
# Test bob (enhanced)
|
|
163
|
+
try:
|
|
164
|
+
test_user("bob", anthropic_service)
|
|
165
|
+
except Exception as e:
|
|
166
|
+
print(f"\nFailed to test bob: {e}")
|
|
167
|
+
|
|
168
|
+
print("\n" + "=" * 60)
|
|
169
|
+
print("Example complete!")
|
|
170
|
+
print("=" * 60)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
if __name__ == "__main__":
|
|
174
|
+
try:
|
|
175
|
+
main()
|
|
176
|
+
except Exception as e:
|
|
177
|
+
err = str(e)
|
|
178
|
+
print("\n" + "=" * 60)
|
|
179
|
+
if "ANTHROPIC_API_KEY" in err or "api_key" in err.lower():
|
|
180
|
+
print("ERROR: Anthropic API key not configured")
|
|
181
|
+
print("Fix: export ANTHROPIC_API_KEY=sk-ant-...")
|
|
182
|
+
elif "Local provider does not support" in err:
|
|
183
|
+
print("ERROR: Identity Provider not configured")
|
|
184
|
+
print("Fix: Console -> Settings -> Identity Bridge")
|
|
185
|
+
print("\nOr run simpler example first:")
|
|
186
|
+
print(" python anthropic_1a_dropin_simple.py")
|
|
187
|
+
else:
|
|
188
|
+
print(f"ERROR: {e}")
|
|
189
|
+
print("=" * 60)
|
|
190
|
+
sys.exit(1)
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
anthropic_1b_multiuser_bind_streaming.py - Multi-user Anthropic with streaming
|
|
4
|
+
|
|
5
|
+
Same as 1b but demonstrates streaming responses with bind_to_user pattern.
|
|
6
|
+
Claude uses messages.stream() context manager pattern.
|
|
7
|
+
|
|
8
|
+
Prerequisites:
|
|
9
|
+
- MACAW SDK installed (pip install macaw-client macaw-adapters)
|
|
10
|
+
- ANTHROPIC_API_KEY environment variable
|
|
11
|
+
- Identity Provider configured (Console -> Settings -> Identity Bridge)
|
|
12
|
+
- Test users: alice/Alice123!, bob/Bob@123!
|
|
13
|
+
|
|
14
|
+
Run:
|
|
15
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
16
|
+
python anthropic_1b_multiuser_bind_streaming.py
|
|
17
|
+
|
|
18
|
+
No IdP configured? Run simpler example first:
|
|
19
|
+
python anthropic_1a_dropin_simple.py
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
|
|
25
|
+
from macaw_adapters.anthropic import SecureAnthropic
|
|
26
|
+
from macaw_client import MACAWClient, RemoteIdentityProvider
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Test configurations based on user policies
|
|
30
|
+
# Alice: Haiku only, max 500 tokens
|
|
31
|
+
# Bob: Haiku/Sonnet/Opus, max 2000 tokens
|
|
32
|
+
USER_TESTS = {
|
|
33
|
+
"alice": {
|
|
34
|
+
"password": "Alice123!",
|
|
35
|
+
"policy_desc": "Claude Haiku only, max 500 tokens",
|
|
36
|
+
"tests": [
|
|
37
|
+
# (model, max_tokens, should_succeed)
|
|
38
|
+
("claude-3-haiku-20240307", 400, True), # ALLOWED
|
|
39
|
+
("claude-opus-4-5-20251101", 400, False), # BLOCKED - alice can't use Opus
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"bob": {
|
|
43
|
+
"password": "Bob@123!",
|
|
44
|
+
"policy_desc": "Claude Haiku/Sonnet/Opus, max 2000 tokens",
|
|
45
|
+
"tests": [
|
|
46
|
+
("claude-3-haiku-20240307", 400, True), # ALLOWED
|
|
47
|
+
("claude-opus-4-5-20251101", 400, True), # ALLOWED - bob CAN use Opus
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def create_user_client(username: str, password: str) -> MACAWClient:
|
|
54
|
+
"""Create authenticated user client."""
|
|
55
|
+
print(f" Authenticating {username}...")
|
|
56
|
+
jwt_token, _ = RemoteIdentityProvider().login(username, password)
|
|
57
|
+
|
|
58
|
+
user = MACAWClient(
|
|
59
|
+
user_name=username,
|
|
60
|
+
iam_token=jwt_token,
|
|
61
|
+
agent_type="user",
|
|
62
|
+
app_name="financial-app"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if not user.register():
|
|
66
|
+
raise RuntimeError(f"Failed to register user {username}")
|
|
67
|
+
|
|
68
|
+
print(f" User agent: {user.agent_id}")
|
|
69
|
+
return user
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_user(username: str, anthropic_service: SecureAnthropic):
|
|
73
|
+
"""Test a user with streaming bind_to_user pattern."""
|
|
74
|
+
config = USER_TESTS[username]
|
|
75
|
+
|
|
76
|
+
print(f"\n{'=' * 60}")
|
|
77
|
+
print(f"Testing {username.upper()} via bind_to_user (STREAMING)")
|
|
78
|
+
print(f"Policy: {config['policy_desc']}")
|
|
79
|
+
print("=" * 60)
|
|
80
|
+
|
|
81
|
+
# 1. Create user client with JWT
|
|
82
|
+
user_client = create_user_client(username, config["password"])
|
|
83
|
+
|
|
84
|
+
# 2. Bind user to service
|
|
85
|
+
print(f"\n Binding to service: {anthropic_service.server_id}")
|
|
86
|
+
user_anthropic = anthropic_service.bind_to_user(user_client)
|
|
87
|
+
|
|
88
|
+
# 3. Test with streaming
|
|
89
|
+
print(f"\n Running streaming tests:")
|
|
90
|
+
for model, max_tokens, should_succeed in config["tests"]:
|
|
91
|
+
expected = "SHOULD SUCCEED" if should_succeed else "SHOULD BE BLOCKED"
|
|
92
|
+
print(f"\n -> {model}, {max_tokens} tokens ({expected})")
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
# Claude streaming uses context manager pattern
|
|
96
|
+
with user_anthropic.messages.stream(
|
|
97
|
+
model=model,
|
|
98
|
+
max_tokens=max_tokens,
|
|
99
|
+
messages=[
|
|
100
|
+
{"role": "user", "content": "What is revenue growth? Brief answer."}
|
|
101
|
+
]
|
|
102
|
+
) as stream:
|
|
103
|
+
if should_succeed:
|
|
104
|
+
print(f" SUCCESS (as expected)")
|
|
105
|
+
print(f" Streaming: ", end="")
|
|
106
|
+
collected = []
|
|
107
|
+
for text in stream.text_stream:
|
|
108
|
+
collected.append(text)
|
|
109
|
+
print(text, end="", flush=True)
|
|
110
|
+
print() # newline
|
|
111
|
+
print(f" Total chars: {len(''.join(collected))}")
|
|
112
|
+
else:
|
|
113
|
+
# If we got here, policy didn't block - unexpected
|
|
114
|
+
for text in stream.text_stream:
|
|
115
|
+
pass # consume stream
|
|
116
|
+
print(f" UNEXPECTED SUCCESS - should have been blocked!")
|
|
117
|
+
|
|
118
|
+
except Exception as e:
|
|
119
|
+
error = str(e)
|
|
120
|
+
is_policy_block = any(word in error.lower() for word in
|
|
121
|
+
["not in allowed", "not permitted", "policy",
|
|
122
|
+
"max_tokens", "model", "blocked", "denied"])
|
|
123
|
+
if is_policy_block:
|
|
124
|
+
if should_succeed:
|
|
125
|
+
print(f" UNEXPECTED BLOCK: {error[:60]}...")
|
|
126
|
+
else:
|
|
127
|
+
print(f" CORRECTLY BLOCKED by policy")
|
|
128
|
+
else:
|
|
129
|
+
print(f" ERROR: {error[:60]}...")
|
|
130
|
+
|
|
131
|
+
# 4. Cleanup
|
|
132
|
+
print(f"\n Unbinding user...")
|
|
133
|
+
user_anthropic.unbind()
|
|
134
|
+
print(f" is_bound: {user_anthropic.is_bound}")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def main():
|
|
138
|
+
# Check for API key
|
|
139
|
+
if not os.environ.get("ANTHROPIC_API_KEY"):
|
|
140
|
+
print("Set ANTHROPIC_API_KEY environment variable")
|
|
141
|
+
print(" export ANTHROPIC_API_KEY=sk-ant-...")
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
print("=" * 60)
|
|
145
|
+
print("Example 1b-stream: Multi-user with Streaming (Anthropic)")
|
|
146
|
+
print("=" * 60)
|
|
147
|
+
print("\nStreaming Pattern: messages.stream() context manager")
|
|
148
|
+
print("Policy enforcement: BEFORE first chunk is returned")
|
|
149
|
+
|
|
150
|
+
# Create SINGLE service (shared across all users)
|
|
151
|
+
print("\n--- Creating SecureAnthropic service ---")
|
|
152
|
+
anthropic_service = SecureAnthropic(app_name="anthropic-service")
|
|
153
|
+
print(f"Service registered: {anthropic_service.server_id}")
|
|
154
|
+
print(f"Mode: {anthropic_service._mode}")
|
|
155
|
+
|
|
156
|
+
# Test alice (restricted)
|
|
157
|
+
try:
|
|
158
|
+
test_user("alice", anthropic_service)
|
|
159
|
+
except Exception as e:
|
|
160
|
+
print(f"\nFailed to test alice: {e}")
|
|
161
|
+
|
|
162
|
+
# Test bob (enhanced)
|
|
163
|
+
try:
|
|
164
|
+
test_user("bob", anthropic_service)
|
|
165
|
+
except Exception as e:
|
|
166
|
+
print(f"\nFailed to test bob: {e}")
|
|
167
|
+
|
|
168
|
+
print("\n" + "=" * 60)
|
|
169
|
+
print("Example complete!")
|
|
170
|
+
print("=" * 60)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
if __name__ == "__main__":
|
|
174
|
+
try:
|
|
175
|
+
main()
|
|
176
|
+
except Exception as e:
|
|
177
|
+
err = str(e)
|
|
178
|
+
print("\n" + "=" * 60)
|
|
179
|
+
if "ANTHROPIC_API_KEY" in err or "api_key" in err.lower():
|
|
180
|
+
print("ERROR: Anthropic API key not configured")
|
|
181
|
+
print("Fix: export ANTHROPIC_API_KEY=sk-ant-...")
|
|
182
|
+
elif "Local provider does not support" in err:
|
|
183
|
+
print("ERROR: Identity Provider not configured")
|
|
184
|
+
print("Fix: Console -> Settings -> Identity Bridge")
|
|
185
|
+
print("\nOr run simpler example first:")
|
|
186
|
+
print(" python anthropic_1a_dropin_simple.py")
|
|
187
|
+
else:
|
|
188
|
+
print(f"ERROR: {e}")
|
|
189
|
+
print("=" * 60)
|
|
190
|
+
sys.exit(1)
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
anthropic_1c_a2a_invoke.py - Agent-to-Agent invocation with invoke_tool
|
|
4
|
+
|
|
5
|
+
Demonstrates explicit tool invocation for agent systems and cross-service calls.
|
|
6
|
+
Use when you need fine-grained control over routing and tool invocation.
|
|
7
|
+
|
|
8
|
+
Prerequisites:
|
|
9
|
+
- MACAW SDK installed (pip install macaw-client macaw-adapters)
|
|
10
|
+
- ANTHROPIC_API_KEY environment variable
|
|
11
|
+
- Identity Provider configured (Console -> Settings -> Identity Bridge)
|
|
12
|
+
- Test users: alice/Alice123!
|
|
13
|
+
|
|
14
|
+
Run:
|
|
15
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
16
|
+
python anthropic_1c_a2a_invoke.py
|
|
17
|
+
|
|
18
|
+
No IdP configured? Run simpler example first:
|
|
19
|
+
python anthropic_1a_dropin_simple.py
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
|
|
25
|
+
from macaw_adapters.anthropic import SecureAnthropic
|
|
26
|
+
from macaw_client import MACAWClient, RemoteIdentityProvider
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def main():
|
|
30
|
+
# Check for API key
|
|
31
|
+
if not os.environ.get("ANTHROPIC_API_KEY"):
|
|
32
|
+
print("Set ANTHROPIC_API_KEY environment variable")
|
|
33
|
+
print(" export ANTHROPIC_API_KEY=sk-ant-...")
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
print("=" * 60)
|
|
37
|
+
print("Example 1c: A2A with invoke_tool (Anthropic)")
|
|
38
|
+
print("=" * 60)
|
|
39
|
+
print("\nPath: invoke_tool (explicit A2A control)")
|
|
40
|
+
print("Use when: Agent systems, explicit control, cross-service calls")
|
|
41
|
+
print("\nArchitecture:")
|
|
42
|
+
print(" - SecureAnthropic service registers tools")
|
|
43
|
+
print(" - User agent calls invoke_tool() directly")
|
|
44
|
+
print(" - Must know MAPL tool name: tool:<app>/generate")
|
|
45
|
+
print(" - Returns raw dict (not SDK types)")
|
|
46
|
+
|
|
47
|
+
# 1. Create Anthropic service
|
|
48
|
+
print("\n--- Creating SecureAnthropic service ---")
|
|
49
|
+
anthropic_service = SecureAnthropic(app_name="anthropic-service")
|
|
50
|
+
print(f"Service registered: {anthropic_service.server_id}")
|
|
51
|
+
print(f"Tools registered:")
|
|
52
|
+
for tool_name in anthropic_service.tools.keys():
|
|
53
|
+
print(f" - {tool_name}")
|
|
54
|
+
|
|
55
|
+
# 2. Create user agent with JWT
|
|
56
|
+
print("\n--- Creating user agent ---")
|
|
57
|
+
print(" Authenticating alice...")
|
|
58
|
+
jwt_token, _ = RemoteIdentityProvider().login("alice", "Alice123!")
|
|
59
|
+
|
|
60
|
+
user = MACAWClient(
|
|
61
|
+
user_name="alice",
|
|
62
|
+
iam_token=jwt_token,
|
|
63
|
+
agent_type="user",
|
|
64
|
+
app_name="my-agent"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if not user.register():
|
|
68
|
+
print(" Failed to register user agent")
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
print(f" User agent: {user.agent_id}")
|
|
72
|
+
|
|
73
|
+
# 3. Explicit invoke_tool
|
|
74
|
+
print("\n--- Test 1: Direct invoke_tool ---")
|
|
75
|
+
print(f" Tool name: tool:{anthropic_service.app_name}/generate")
|
|
76
|
+
print(f" Target: {anthropic_service.server_id}")
|
|
77
|
+
|
|
78
|
+
result = user.invoke_tool(
|
|
79
|
+
tool_name=f"tool:{anthropic_service.app_name}/generate",
|
|
80
|
+
parameters={
|
|
81
|
+
"model": "claude-3-haiku-20240307",
|
|
82
|
+
"max_tokens": 100,
|
|
83
|
+
"messages": [
|
|
84
|
+
{"role": "user", "content": "What is compound interest? Brief answer."}
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
target_agent=anthropic_service.server_id
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Result is raw dict - you handle parsing
|
|
91
|
+
if isinstance(result, dict):
|
|
92
|
+
if "error" in result:
|
|
93
|
+
print(f" Error: {result['error']}")
|
|
94
|
+
elif "content" in result:
|
|
95
|
+
# Claude response format
|
|
96
|
+
content = result["content"][0]["text"]
|
|
97
|
+
print(f" Success!")
|
|
98
|
+
print(f" Model: {result.get('model')}")
|
|
99
|
+
print(f" Response: {content[:80]}...")
|
|
100
|
+
else:
|
|
101
|
+
print(f" Unexpected result: {result}")
|
|
102
|
+
|
|
103
|
+
# 4. Test policy enforcement
|
|
104
|
+
print("\n--- Test 2: Policy enforcement (Opus blocked for alice) ---")
|
|
105
|
+
try:
|
|
106
|
+
result = user.invoke_tool(
|
|
107
|
+
tool_name=f"tool:{anthropic_service.app_name}/generate",
|
|
108
|
+
parameters={
|
|
109
|
+
"model": "claude-opus-4-5-20251101", # Alice can't use Opus!
|
|
110
|
+
"max_tokens": 100,
|
|
111
|
+
"messages": [{"role": "user", "content": "Hello"}]
|
|
112
|
+
},
|
|
113
|
+
target_agent=anthropic_service.server_id
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if isinstance(result, dict) and "error" in result:
|
|
117
|
+
print(f" Correctly blocked: {result['error'][:60]}...")
|
|
118
|
+
else:
|
|
119
|
+
print(f" Unexpected - should have been blocked!")
|
|
120
|
+
except Exception as e:
|
|
121
|
+
print(f" Correctly blocked: {str(e)[:60]}...")
|
|
122
|
+
|
|
123
|
+
# 5. Service discovery (optional advanced feature)
|
|
124
|
+
print("\n--- Service Discovery ---")
|
|
125
|
+
print(" (Agents can discover services dynamically via registry)")
|
|
126
|
+
print(f" Known service: {anthropic_service.server_id}")
|
|
127
|
+
print(f" Tool pattern: tool:<app_name>/generate")
|
|
128
|
+
|
|
129
|
+
print("\n" + "=" * 60)
|
|
130
|
+
print("Example complete!")
|
|
131
|
+
print("=" * 60)
|
|
132
|
+
print("\nKey differences from bind_to_user:")
|
|
133
|
+
print(" - Must know MAPL tool names (tool:xxx/generate)")
|
|
134
|
+
print(" - Must know target agent server_id")
|
|
135
|
+
print(" - Returns raw dict (not Message)")
|
|
136
|
+
print(" - More control, but more responsibility")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if __name__ == "__main__":
|
|
140
|
+
try:
|
|
141
|
+
main()
|
|
142
|
+
except Exception as e:
|
|
143
|
+
err = str(e)
|
|
144
|
+
print("\n" + "=" * 60)
|
|
145
|
+
if "ANTHROPIC_API_KEY" in err or "api_key" in err.lower():
|
|
146
|
+
print("ERROR: Anthropic API key not configured")
|
|
147
|
+
print("Fix: export ANTHROPIC_API_KEY=sk-ant-...")
|
|
148
|
+
elif "Local provider does not support" in err:
|
|
149
|
+
print("ERROR: Identity Provider not configured")
|
|
150
|
+
print("Fix: Console -> Settings -> Identity Bridge")
|
|
151
|
+
print("\nOr run simpler example first:")
|
|
152
|
+
print(" python anthropic_1a_dropin_simple.py")
|
|
153
|
+
else:
|
|
154
|
+
print(f"ERROR: {e}")
|
|
155
|
+
print("=" * 60)
|
|
156
|
+
sys.exit(1)
|