@macaw-ai/client 0.0.0-stage → 0.9.10

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.
Files changed (142) hide show
  1. package/LICENSE.md +339 -0
  2. package/bin/install.js +278 -0
  3. package/package.json +37 -4
  4. package/payload/README.md +56 -0
  5. package/payload/secureAI/LICENSE +190 -0
  6. package/payload/secureAI/NOTICE +5 -0
  7. package/payload/secureAI/demos/policy-controls/1_uncontrolled.py +139 -0
  8. package/payload/secureAI/demos/policy-controls/2_with_macaw.py +139 -0
  9. package/payload/secureAI/demos/policy-controls/3_with_user_policies.py +162 -0
  10. package/payload/secureAI/demos/policy-controls/credentials.txt +12 -0
  11. package/payload/secureAI/demos/policy-controls/malicious_report.txt +21 -0
  12. package/payload/secureAI/demos/policy-controls/sales_report.txt +20 -0
  13. package/payload/secureAI/demos/tutorial-1-rbac/README.md +143 -0
  14. package/payload/secureAI/demos/tutorial-1-rbac/app.py +147 -0
  15. package/payload/secureAI/demos/tutorial-1-rbac/authprompts_demo.py +306 -0
  16. package/payload/secureAI/demos/tutorial-1-rbac/config/claims-config.yaml +152 -0
  17. package/payload/secureAI/demos/tutorial-1-rbac/demo_secureopenai_with_policies.py +150 -0
  18. package/payload/secureAI/demos/tutorial-1-rbac/policies/bu_Analytics.json +8 -0
  19. package/payload/secureAI/demos/tutorial-1-rbac/policies/company_FinTech_Corp.json +70 -0
  20. package/payload/secureAI/demos/tutorial-1-rbac/policies/llm-base.json +69 -0
  21. package/payload/secureAI/demos/tutorial-1-rbac/policies/team_Reporting.json +8 -0
  22. package/payload/secureAI/demos/tutorial-1-rbac/policies/user_alice.json +32 -0
  23. package/payload/secureAI/demos/tutorial-1-rbac/policies/user_bob.json +28 -0
  24. package/payload/secureAI/demos/tutorial-1-rbac/policies/user_carol.json +34 -0
  25. package/payload/secureAI/demos/tutorial-1-rbac/requirements.txt +6 -0
  26. package/payload/secureAI/demos/tutorial-1-rbac/setup/auth0_complete_setup.sh +392 -0
  27. package/payload/secureAI/demos/tutorial-1-rbac/setup/keycloak_complete_setup.sh +320 -0
  28. package/payload/secureAI/demos/tutorial-2-intent/Policies/alation-remote-proxy.json +155 -0
  29. package/payload/secureAI/demos/tutorial-2-intent/Policies/bu.json +22 -0
  30. package/payload/secureAI/demos/tutorial-2-intent/Policies/company.json +17 -0
  31. package/payload/secureAI/demos/tutorial-2-intent/Policies/user_alice.json +48 -0
  32. package/payload/secureAI/demos/tutorial-2-intent/Policies/user_bob.json +83 -0
  33. package/payload/secureAI/demos/tutorial-2-intent/README.md +213 -0
  34. package/payload/secureAI/demos/tutorial-2-intent/script.py +188 -0
  35. package/payload/secureAI/demos/tutorial-2-intent/setup/alation_verifier.py +177 -0
  36. package/payload/secureAI/demos/tutorial-2-intent/setup/get_alation_user_token.py +180 -0
  37. package/payload/secureAI/examples/README.md +244 -0
  38. package/payload/secureAI/examples/anthropic/anthropic_1a_dropin_simple.py +103 -0
  39. package/payload/secureAI/examples/anthropic/anthropic_1b_multiuser_bind.py +190 -0
  40. package/payload/secureAI/examples/anthropic/anthropic_1b_multiuser_bind_streaming.py +190 -0
  41. package/payload/secureAI/examples/anthropic/anthropic_1c_a2a_invoke.py +156 -0
  42. package/payload/secureAI/examples/attestations/1a_trade_alice.py +215 -0
  43. package/payload/secureAI/examples/attestations/1a_trade_bob.py +164 -0
  44. package/payload/secureAI/examples/attestations/1b_grant_admin.py +170 -0
  45. package/payload/secureAI/examples/attestations/1b_grant_researcher.py +231 -0
  46. package/payload/secureAI/examples/attestations/README.md +128 -0
  47. package/payload/secureAI/examples/langchain/langchain_1a_dropin_simple.py +199 -0
  48. package/payload/secureAI/examples/langchain/langchain_1b_multiuser.py +262 -0
  49. package/payload/secureAI/examples/langchain/langchain_1c_orchestration.py +332 -0
  50. package/payload/secureAI/examples/langchain/langchain_1d_llm_openai.py +130 -0
  51. package/payload/secureAI/examples/langchain/langchain_1e_llm_anthropic.py +130 -0
  52. package/payload/secureAI/examples/langchain/langchain_1f_memory.py +214 -0
  53. package/payload/secureAI/examples/litellm/litellm_1a_dropin_simple.py +131 -0
  54. package/payload/secureAI/examples/mcp/1a_simple_invocation.py +115 -0
  55. package/payload/secureAI/examples/mcp/1b_discovery_and_resources.py +142 -0
  56. package/payload/secureAI/examples/mcp/1c_logging_client.py +100 -0
  57. package/payload/secureAI/examples/mcp/1d_progress_client.py +100 -0
  58. package/payload/secureAI/examples/mcp/1e_sampling_client.py +150 -0
  59. package/payload/secureAI/examples/mcp/1e_sampling_server.py +90 -0
  60. package/payload/secureAI/examples/mcp/1f_elicitation_client.py +127 -0
  61. package/payload/secureAI/examples/mcp/1f_elicitation_server.py +73 -0
  62. package/payload/secureAI/examples/mcp/1g_roots_client.py +122 -0
  63. package/payload/secureAI/examples/mcp/1g_roots_server.py +85 -0
  64. package/payload/secureAI/examples/mcp/2a_proxy_snowflake.py +212 -0
  65. package/payload/secureAI/examples/mcp/2b_proxy_databricks.py +299 -0
  66. package/payload/secureAI/examples/mcp/2c_proxy_stdio_fetch.py +91 -0
  67. package/payload/secureAI/examples/mcp/2d_proxy_stdio_salesforce.py +119 -0
  68. package/payload/secureAI/examples/mcp/2e_proxy_multiuser.py +136 -0
  69. package/payload/secureAI/examples/mcp/2f_proxy_stdio_databricks.py +144 -0
  70. package/payload/secureAI/examples/mcp/3a_native_mcp_client.py +104 -0
  71. package/payload/secureAI/examples/mcp/3b_native_mcp_proxy.py +114 -0
  72. package/payload/secureAI/examples/mcp/3c_native_mcp_sampling.py +180 -0
  73. package/payload/secureAI/examples/mcp/3d_native_mcp_2026.py +135 -0
  74. package/payload/secureAI/examples/mcp/securemcp_calculator.py +248 -0
  75. package/payload/secureAI/examples/mcp/securemcp_server.py +75 -0
  76. package/payload/secureAI/examples/openai/openai_1a_dropin_simple.py +102 -0
  77. package/payload/secureAI/examples/openai/openai_1b_multiuser_bind.py +193 -0
  78. package/payload/secureAI/examples/openai/openai_1b_multiuser_bind_streaming.py +199 -0
  79. package/payload/secureAI/examples/openai/openai_1c_a2a_invoke.py +156 -0
  80. package/payload/secureAI/examples/openai/openai_1d_invocation_patterns.py +337 -0
  81. package/payload/secureAI/examples/phishing-resistance/1a_login_alice.py +180 -0
  82. package/payload/secureAI/examples/phishing-resistance/1b_trade_alice.py +241 -0
  83. package/payload/secureAI/examples/phishing-resistance/1b_trade_bob.py +164 -0
  84. package/payload/secureAI/examples/phishing-resistance/README.md +124 -0
  85. package/payload/secureAI/examples/phishing-resistance/get_token.py +114 -0
  86. package/payload/secureAI/examples/pydantic_ai/README.md +88 -0
  87. package/payload/secureAI/examples/pydantic_ai/policies/app_pydantic-agent.json +10 -0
  88. package/payload/secureAI/examples/pydantic_ai/pydanticai_1a_dropin_simple.py +90 -0
  89. package/payload/secureAI/examples/pydantic_ai/pydanticai_1b_model_routing.py +85 -0
  90. package/payload/secureAI/examples/pydantic_ai/pydanticai_1c_native_tools.py +83 -0
  91. package/payload/secureAI/examples/pydantic_ai/pydanticai_1d_multiuser_bind.py +104 -0
  92. package/payload/secureAI/examples/pydantic_ai/pydanticai_1e_mcp_compose.py +92 -0
  93. package/payload/secureAI/macaw_adapters/__init__.py +55 -0
  94. package/payload/secureAI/macaw_adapters/anthropic/REFERENCE.md +374 -0
  95. package/payload/secureAI/macaw_adapters/anthropic/__init__.py +22 -0
  96. package/payload/secureAI/macaw_adapters/anthropic/secure_anthropic.py +792 -0
  97. package/payload/secureAI/macaw_adapters/anthropic_ext_tools/__init__.py +25 -0
  98. package/payload/secureAI/macaw_adapters/anthropic_ext_tools/secure_anthropic.py +840 -0
  99. package/payload/secureAI/macaw_adapters/langchain/REFERENCE.md +234 -0
  100. package/payload/secureAI/macaw_adapters/langchain/__init__.py +100 -0
  101. package/payload/secureAI/macaw_adapters/langchain/_utils.py +83 -0
  102. package/payload/secureAI/macaw_adapters/langchain/agents.py +296 -0
  103. package/payload/secureAI/macaw_adapters/langchain/anthropic.py +456 -0
  104. package/payload/secureAI/macaw_adapters/langchain/callbacks.py +306 -0
  105. package/payload/secureAI/macaw_adapters/langchain/memory.py +352 -0
  106. package/payload/secureAI/macaw_adapters/langchain/openai.py +410 -0
  107. package/payload/secureAI/macaw_adapters/langchain/tools.py +174 -0
  108. package/payload/secureAI/macaw_adapters/litellm/README.md +216 -0
  109. package/payload/secureAI/macaw_adapters/litellm/__init__.py +143 -0
  110. package/payload/secureAI/macaw_adapters/litellm/secure_litellm.py +686 -0
  111. package/payload/secureAI/macaw_adapters/mcp/REFERENCE.md +460 -0
  112. package/payload/secureAI/macaw_adapters/mcp/__init__.py +65 -0
  113. package/payload/secureAI/macaw_adapters/mcp/_endpoint.py +193 -0
  114. package/payload/secureAI/macaw_adapters/mcp/_endpoint2.py +225 -0
  115. package/payload/secureAI/macaw_adapters/mcp/_serve.py +25 -0
  116. package/payload/secureAI/macaw_adapters/mcp/client.py +721 -0
  117. package/payload/secureAI/macaw_adapters/mcp/decorators.py +228 -0
  118. package/payload/secureAI/macaw_adapters/mcp/fastmcp_compat.py +353 -0
  119. package/payload/secureAI/macaw_adapters/mcp/mcp.py +785 -0
  120. package/payload/secureAI/macaw_adapters/mcp/proxy.py +626 -0
  121. package/payload/secureAI/macaw_adapters/mcp/server.py +317 -0
  122. package/payload/secureAI/macaw_adapters/mcp/subscriptions.py +450 -0
  123. package/payload/secureAI/macaw_adapters/openai/REFERENCE.md +368 -0
  124. package/payload/secureAI/macaw_adapters/openai/__init__.py +22 -0
  125. package/payload/secureAI/macaw_adapters/openai/secure_openai.py +833 -0
  126. package/payload/secureAI/macaw_adapters/openai_ext_tools/REFERENCE.md +368 -0
  127. package/payload/secureAI/macaw_adapters/openai_ext_tools/__init__.py +25 -0
  128. package/payload/secureAI/macaw_adapters/openai_ext_tools/secure_openai.py +876 -0
  129. package/payload/secureAI/macaw_adapters/pydantic_ai/__init__.py +36 -0
  130. package/payload/secureAI/macaw_adapters/pydantic_ai/_core.py +257 -0
  131. package/payload/secureAI/macaw_adapters/pydantic_ai/agent.py +218 -0
  132. package/payload/secureAI/macaw_adapters/pydantic_ai/model.py +99 -0
  133. package/payload/secureAI/macaw_adapters/pydantic_ai/toolset.py +86 -0
  134. package/payload/secureAI/test_harness_quick.py +296 -0
  135. package/wheels/macaw_adapters-0.9.10.1-py3-none-any.whl +0 -0
  136. package/wheels/macaw_client-0.9.10.1-cp312-cp312-macosx_11_0_arm64.whl +0 -0
  137. package/wheels/macaw_client-0.9.10.1-cp312-cp312-manylinux_2_17_x86_64.whl +0 -0
  138. package/wheels/macaw_client-0.9.10.1-cp314-cp314-macosx_11_0_arm64.whl +0 -0
  139. package/wheels/macaw_client-0.9.10.1-cp314-cp314-manylinux_2_17_x86_64.whl +0 -0
  140. package/wheels/macaw_client-0.9.10.1-cp314-cp314-win_amd64.whl +0 -0
  141. package/wheels/macaw_client-0.9.10.1-cp39-cp39-macosx_11_0_arm64.whl +0 -0
  142. 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)