@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.
Files changed (142) hide show
  1. package/LICENSE.md +339 -0
  2. package/bin/install.js +422 -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,231 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 1b_grant_researcher.py - Reusable Capability Grant (Researcher)
4
+
5
+ A researcher needs admin approval to use web_search.
6
+ - First invocation: BLOCKS until admin approves
7
+ - Second+ invocations: IMMEDIATE (reuses existing approval)
8
+
9
+ The key difference from Example 1a is: one_time=False
10
+
11
+ This demonstrates:
12
+ - Reusable grants: approve once, use many times
13
+ - time_to_live: how long the grant lasts
14
+ - attestation_accessed audit event on reuse
15
+
16
+ NOTE: This example requires interactive approval and is not suitable
17
+ for automated test harnesses.
18
+
19
+ Prerequisites:
20
+ - MACAW SDK installed (pip install macaw-client macaw-adapters)
21
+ - Identity Provider configured (Console -> Settings -> Identity Bridge)
22
+ - Test users: alice/Alice123!, bob/Bob@123! (bob needs admin role)
23
+
24
+ Run:
25
+ # Terminal 1: Run researcher request (will block waiting)
26
+ python 1b_grant_researcher.py
27
+
28
+ # Terminal 2: Run admin approval
29
+ python 1b_grant_admin.py
30
+ """
31
+
32
+ import json
33
+ import time
34
+
35
+ from macaw_client import MACAWClient, RemoteIdentityProvider
36
+
37
+
38
+ def web_search_handler(params):
39
+ """Simulate web search."""
40
+ query = params.get('query', '')
41
+ print(f"\n [TOOL] web_search invoked!")
42
+ print(f" [TOOL] Query: {query}")
43
+
44
+ # Simulate search results
45
+ result = {
46
+ "status": "success",
47
+ "query": query,
48
+ "results": [
49
+ {"title": f"Result 1 for '{query}'", "url": "https://example.com/1"},
50
+ {"title": f"Result 2 for '{query}'", "url": "https://example.com/2"},
51
+ ]
52
+ }
53
+ print(f" [TOOL] Returning {len(result['results'])} results")
54
+ return result
55
+
56
+
57
+ def main():
58
+ print("=" * 60)
59
+ print("Example 1b: Reusable Capability Grant - Researcher")
60
+ print("=" * 60)
61
+
62
+ # Step 1: Create Search Service
63
+ print("\n[Step 1] Creating Search Service...")
64
+
65
+ try:
66
+ search_service = MACAWClient(
67
+ app_name="search",
68
+ agent_type="service",
69
+ tools={
70
+ "tool:search/web_search": {
71
+ "handler": web_search_handler,
72
+ "description": "Search the web for information"
73
+ }
74
+ }
75
+ )
76
+
77
+ if not search_service.register():
78
+ print(" ERROR: Failed to register Search Service")
79
+ return 1
80
+
81
+ print(f" Service ID: {search_service.agent_id}")
82
+ print(f" Provides: tool:search/web_search")
83
+
84
+ except Exception as e:
85
+ print(f" ERROR: {e}")
86
+ return 1
87
+
88
+ # Step 2: Authenticate as researcher (using alice credentials)
89
+ print("\n[Step 2] Authenticating as researcher...")
90
+ try:
91
+ jwt_token, _ = RemoteIdentityProvider().login("alice", "Alice123!")
92
+ print(" Got JWT token")
93
+ except Exception as e:
94
+ print(f" ERROR: Failed to authenticate: {e}")
95
+ search_service.unregister()
96
+ return 1
97
+
98
+ # Step 3: Create researcher agent with REUSABLE attestation (one_time=False)
99
+ print("\n[Step 3] Creating researcher agent...")
100
+ try:
101
+ researcher = MACAWClient(
102
+ user_name="researcher",
103
+ iam_token=jwt_token,
104
+ agent_type="user",
105
+ app_name="research-app",
106
+ intent_policy={
107
+ "resources": ["tool:search/web_search"],
108
+ # Attestation always required for this tool
109
+ "attestations": ["capability_web_search"],
110
+ # Attestation metadata - REUSABLE
111
+ "constraints": {
112
+ "attestations": {
113
+ "capability_web_search": {
114
+ "approval_criteria": "role:admin",
115
+ "timeout": 300, # 5 min to approve
116
+ "one_time": False, # KEY: Reusable!
117
+ "time_to_live": 3600 # Valid for 1 hour (or None for forever)
118
+ }
119
+ }
120
+ }
121
+ }
122
+ )
123
+
124
+ if not researcher.register():
125
+ print(" ERROR: Failed to register researcher agent")
126
+ search_service.unregister()
127
+ return 1
128
+
129
+ print(f" Agent ID: {researcher.agent_id}")
130
+ print(f" one_time=False (REUSABLE capability)")
131
+
132
+ except Exception as e:
133
+ print(f" ERROR: {e}")
134
+ search_service.unregister()
135
+ return 1
136
+
137
+ # Step 4: First invocation - should BLOCK for approval
138
+ print("\n" + "=" * 60)
139
+ print("FIRST INVOCATION - Will block for admin approval")
140
+ print("=" * 60)
141
+ print("\n >>> Run 1b_grant_admin.py in another terminal <<<")
142
+ print("-" * 60)
143
+
144
+ start_time = time.time()
145
+ try:
146
+ result = researcher.invoke_tool(
147
+ tool_name="tool:search/web_search",
148
+ target_agent=search_service.agent_id,
149
+ parameters={"query": "MACAW security framework"}
150
+ )
151
+ elapsed = time.time() - start_time
152
+ print(f"\n FIRST SEARCH COMPLETED! (took {elapsed:.1f}s)")
153
+ print(f" Result: {json.dumps(result, indent=2)}")
154
+
155
+ except Exception as e:
156
+ print(f"\n ERROR on first invocation: {e}")
157
+ researcher.unregister()
158
+ search_service.unregister()
159
+ return 1
160
+
161
+ # Step 5: Second invocation - should be IMMEDIATE (no blocking!)
162
+ print("\n" + "=" * 60)
163
+ print("SECOND INVOCATION - Should be IMMEDIATE (reusing approval)")
164
+ print("=" * 60)
165
+
166
+ start_time = time.time()
167
+ try:
168
+ result = researcher.invoke_tool(
169
+ tool_name="tool:search/web_search",
170
+ target_agent=search_service.agent_id,
171
+ parameters={"query": "AI agent governance"}
172
+ )
173
+ elapsed = time.time() - start_time
174
+ print(f"\n SECOND SEARCH COMPLETED! (took {elapsed:.1f}s)")
175
+ if elapsed < 2.0:
176
+ print(" *** REUSED EXISTING APPROVAL - NO BLOCKING! ***")
177
+ print(f" Result: {json.dumps(result, indent=2)}")
178
+
179
+ except Exception as e:
180
+ print(f"\n ERROR on second invocation: {e}")
181
+
182
+ # Step 6: Third invocation - also immediate
183
+ print("\n" + "=" * 60)
184
+ print("THIRD INVOCATION - Also immediate")
185
+ print("=" * 60)
186
+
187
+ start_time = time.time()
188
+ try:
189
+ result = researcher.invoke_tool(
190
+ tool_name="tool:search/web_search",
191
+ target_agent=search_service.agent_id,
192
+ parameters={"query": "enterprise AI security"}
193
+ )
194
+ elapsed = time.time() - start_time
195
+ print(f"\n THIRD SEARCH COMPLETED! (took {elapsed:.1f}s)")
196
+ if elapsed < 2.0:
197
+ print(" *** REUSED EXISTING APPROVAL - NO BLOCKING! ***")
198
+
199
+ except Exception as e:
200
+ print(f"\n ERROR on third invocation: {e}")
201
+
202
+ # Cleanup
203
+ print("\n[Cleanup] Unregistering agents...")
204
+ researcher.unregister()
205
+ search_service.unregister()
206
+
207
+ print("\n" + "=" * 60)
208
+ print("Example 1b Complete!")
209
+ print("First invocation required approval, subsequent were immediate.")
210
+ print("=" * 60)
211
+
212
+ return 0
213
+
214
+
215
+ if __name__ == "__main__":
216
+ import sys
217
+ try:
218
+ sys.exit(main())
219
+ except Exception as e:
220
+ err = str(e)
221
+ print("\n" + "=" * 60)
222
+ if "Local provider does not support" in err:
223
+ print("ERROR: Identity Provider not configured")
224
+ print("Fix: Console -> Settings -> Identity Bridge")
225
+ elif "Connection refused" in err or "connect" in err.lower():
226
+ print("ERROR: Cannot connect to MACAW")
227
+ print("Fix: Ensure LocalAgent is running")
228
+ else:
229
+ print(f"ERROR: {e}")
230
+ print("=" * 60)
231
+ sys.exit(1)
@@ -0,0 +1,128 @@
1
+ # External Attestations Examples
2
+
3
+ These examples demonstrate **human-in-the-loop approval workflows** using MACAW's external attestation system.
4
+
5
+ ## What Are External Attestations?
6
+
7
+ External attestations require a **third party** (manager, admin, compliance officer) to approve an operation before it can proceed. The request **blocks** while waiting for approval.
8
+
9
+ **Use cases:**
10
+ - High-value trades requiring manager sign-off
11
+ - Sensitive operations needing compliance review
12
+ - Capability grants for elevated access
13
+ - Time-boxed permissions
14
+
15
+ ## Examples
16
+
17
+ ### Example 1a: One-Time Trade Approval
18
+
19
+ A high-value trade ($15,000) requires manager approval. Each trade needs fresh approval.
20
+
21
+ | File | Role | What It Does |
22
+ |------|------|--------------|
23
+ | `1a_trade_alice.py` | Requester | Requests trade, blocks waiting for approval |
24
+ | `1a_trade_bob.py` | Approver | Lists pending requests, approves/denies |
25
+
26
+ **Key concepts:**
27
+ - Conditional attestation: `trade-approved::{params.amount > 10000}`
28
+ - Role-based approval: `approval_criteria: "role:manager"`
29
+ - One-time use: `one_time: True` (consumed after single use)
30
+
31
+ **Run:**
32
+ ```bash
33
+ # Terminal 1: Alice requests trade (blocks)
34
+ python 1a_trade_alice.py
35
+
36
+ # Terminal 2: Bob approves (while Alice waits)
37
+ python 1a_trade_bob.py
38
+ ```
39
+
40
+ ### Example 1b: Reusable Capability Grant
41
+
42
+ A researcher needs admin approval for web search capability. Approve once, use many times.
43
+
44
+ | File | Role | What It Does |
45
+ |------|------|--------------|
46
+ | `1b_grant_researcher.py` | Requester | First call blocks, subsequent calls instant |
47
+ | `1b_grant_admin.py` | Approver | Grants reusable capability |
48
+
49
+ **Key concepts:**
50
+ - Reusable grant: `one_time: False`
51
+ - Time-to-live: `time_to_live: 3600` (1 hour)
52
+ - First call blocks, second+ calls immediate
53
+
54
+ **Run:**
55
+ ```bash
56
+ # Terminal 1: Researcher requests (blocks on first call)
57
+ python 1b_grant_researcher.py
58
+
59
+ # Terminal 2: Admin approves (while researcher waits)
60
+ python 1b_grant_admin.py
61
+
62
+ # Watch Terminal 1: First call completes, then 2nd and 3rd are instant!
63
+ ```
64
+
65
+ ## Prerequisites
66
+
67
+ 1. **MACAW LocalAgent running:**
68
+ ```bash
69
+ python3 -m macaw_agent.main
70
+ ```
71
+
72
+ 2. **Identity provider configured** (Keycloak or Auth0)
73
+ - See `demos/tutorial-1/setup/` for setup scripts
74
+
75
+ 3. **Users created in IDP:**
76
+ - `alice` - trader/researcher
77
+ - `bob` - manager/admin with appropriate role
78
+
79
+ ## Key APIs
80
+
81
+ ### Requester Side
82
+
83
+ ```python
84
+ from macaw_client import MACAWClient
85
+
86
+ # Define attestation in policy
87
+ intent_policy = {
88
+ "attestations": ["trade-approved::{params.amount > 10000}"],
89
+ "constraints": {
90
+ "attestations": {
91
+ "trade-approved": {
92
+ "approval_criteria": "role:manager",
93
+ "timeout": 300, # Block up to 5 minutes
94
+ "one_time": True # Or False for reusable
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ # invoke_tool blocks until approved/denied/timeout
101
+ result = client.invoke_tool("tool:trading/execute", params)
102
+ ```
103
+
104
+ ### Approver Side
105
+
106
+ ```python
107
+ from macaw_client import MACAWClient
108
+
109
+ # List pending attestations you can approve
110
+ attestations = client.list_attestations(status="pending")
111
+
112
+ # Approve or deny
113
+ client.approve_attestation(att, reason="Approved for Q4")
114
+ client.deny_attestation(att, reason="Budget exceeded")
115
+ ```
116
+
117
+ ## One-Time vs Reusable
118
+
119
+ | Setting | Behavior | Use Case |
120
+ |---------|----------|----------|
121
+ | `one_time: True` | Consumed after single use | High-value trades |
122
+ | `one_time: False` | Reusable until TTL expires | Capability grants |
123
+
124
+ ## Learn More
125
+
126
+ - **MAPL Policy Guide**: Full attestation syntax and semantics
127
+ - **Dev Hub Tutorials**: Step-by-step walkthrough with diagrams
128
+ - **Console > Activity**: View attestation audit events
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ langchain_1a_dropin_simple.py - Drop-in replacement for LangChain agents
4
+
5
+ The simplest integration path - replace your LangChain imports and add policy.
6
+ No Identity Provider needed.
7
+
8
+ Prerequisites:
9
+ - MACAW SDK installed (pip install macaw-client macaw-adapters)
10
+ - OPENAI_API_KEY environment variable (for ChatOpenAI)
11
+
12
+ Run:
13
+ export OPENAI_API_KEY=sk-...
14
+ python langchain_1a_dropin_simple.py
15
+ """
16
+
17
+ import os
18
+ import sys
19
+
20
+ # BEFORE: from langchain.agents import create_react_agent, AgentExecutor
21
+ # AFTER:
22
+ from macaw_adapters.langchain import create_react_agent, AgentExecutor, cleanup
23
+
24
+ from langchain_openai import ChatOpenAI
25
+ from langchain.tools import Tool
26
+ from langchain.prompts import PromptTemplate
27
+
28
+
29
+ # Define some tools
30
+ def calculator(expression: str) -> str:
31
+ """Evaluate a math expression."""
32
+ try:
33
+ result = eval(expression)
34
+ return f"Result: {result}"
35
+ except Exception as e:
36
+ return f"Error: {e}"
37
+
38
+
39
+ def get_weather(city: str) -> str:
40
+ """Get weather for a city (mock)."""
41
+ # Mock weather data
42
+ weather_data = {
43
+ "new york": "72°F, Sunny",
44
+ "london": "58°F, Cloudy",
45
+ "tokyo": "68°F, Clear"
46
+ }
47
+ return weather_data.get(city.lower(), f"Weather for {city}: 65°F, Partly cloudy")
48
+
49
+
50
+ def admin_tool(command: str) -> str:
51
+ """Execute admin command (dangerous!)."""
52
+ return f"Executed admin command: {command}"
53
+
54
+
55
+ # Create tools list
56
+ TOOLS = [
57
+ Tool(
58
+ name="calculator",
59
+ func=calculator,
60
+ description="Useful for math calculations. Input should be a math expression."
61
+ ),
62
+ Tool(
63
+ name="weather",
64
+ func=get_weather,
65
+ description="Get current weather for a city. Input should be a city name."
66
+ ),
67
+ Tool(
68
+ name="admin",
69
+ func=admin_tool,
70
+ description="Execute admin commands. Input should be a command string."
71
+ ),
72
+ ]
73
+
74
+
75
+ # Simple ReAct prompt template
76
+ REACT_PROMPT = PromptTemplate.from_template("""Answer the following questions as best you can. You have access to the following tools:
77
+
78
+ {tools}
79
+
80
+ Use the following format:
81
+
82
+ Question: the input question you must answer
83
+ Thought: you should always think about what to do
84
+ Action: the action to take, should be one of [{tool_names}]
85
+ Action Input: the input to the action
86
+ Observation: the result of the action
87
+ ... (this Thought/Action/Action Input/Observation can repeat N times)
88
+ Thought: I now know the final answer
89
+ Final Answer: the final answer to the original input question
90
+
91
+ Begin!
92
+
93
+ Question: {input}
94
+ Thought:{agent_scratchpad}""")
95
+
96
+
97
+ def main():
98
+ # Check for API key
99
+ if not os.environ.get("OPENAI_API_KEY"):
100
+ print("Set OPENAI_API_KEY environment variable")
101
+ print(" export OPENAI_API_KEY=sk-...")
102
+ return
103
+
104
+ print("=" * 60)
105
+ print("Example 1a: Drop-in Replacement (LangChain)")
106
+ print("=" * 60)
107
+ print("\nPath: Direct agent with security_policy")
108
+ print("Use when: Single app, need tool access control")
109
+
110
+ # Create LLM
111
+ llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
112
+
113
+ # Create agent WITH security policy
114
+ # - Only "calculator" and "weather" are allowed
115
+ # - "admin" tool is blocked
116
+ # - Queries containing "password" are blocked
117
+ print("\n--- Creating secure agent ---")
118
+ print("Policy: Allow calculator & weather, block admin")
119
+ print(" Block queries with '*password*'")
120
+
121
+ # MAPL format policy - direct, no conversion layer
122
+ policy = {
123
+ "resources": ["tool:calculator", "tool:weather"], # Allowed tools
124
+ "denied_resources": ["tool:admin"], # Blocked tools
125
+ "constraints": {
126
+ "denied_parameters": {
127
+ "tool:*": {"input": ["*password*", "*secret*"]} # Block sensitive queries
128
+ }
129
+ }
130
+ }
131
+
132
+ agent = create_react_agent(
133
+ llm=llm,
134
+ tools=TOOLS,
135
+ prompt=REACT_PROMPT,
136
+ security_policy=policy
137
+ )
138
+
139
+ # Create executor with same policy
140
+ executor = AgentExecutor(
141
+ agent=agent,
142
+ tools=TOOLS,
143
+ security_policy=policy,
144
+ verbose=True,
145
+ handle_parsing_errors=True,
146
+ max_iterations=3
147
+ )
148
+
149
+ # Test 1: Allowed tool (calculator)
150
+ print("\n--- Test 1: Allowed tool (calculator) ---")
151
+ try:
152
+ result = executor.invoke({"input": "What is 15 * 7 + 23?"})
153
+ print(f"Result: {result.get('output', result)}")
154
+ except Exception as e:
155
+ print(f"Error: {e}")
156
+
157
+ # Test 2: Allowed tool (weather)
158
+ print("\n--- Test 2: Allowed tool (weather) ---")
159
+ try:
160
+ result = executor.invoke({"input": "What's the weather in Tokyo?"})
161
+ print(f"Result: {result.get('output', result)}")
162
+ except Exception as e:
163
+ print(f"Error: {e}")
164
+
165
+ # Test 3: Blocked tool (admin) - should be denied
166
+ print("\n--- Test 3: Blocked tool (admin) - SHOULD BE DENIED ---")
167
+ try:
168
+ result = executor.invoke({"input": "Run admin command: list users"})
169
+ print(f"Result: {result.get('output', result)}")
170
+ # If agent can't use admin tool, it should say it can't do this
171
+ except Exception as e:
172
+ error_msg = str(e).lower()
173
+ if "denied" in error_msg or "blocked" in error_msg or "policy" in error_msg:
174
+ print(f"Correctly blocked: {e}")
175
+ else:
176
+ print(f"Error: {e}")
177
+
178
+ # Cleanup
179
+ print("\n--- Cleanup ---")
180
+ cleanup()
181
+
182
+ print("\n" + "=" * 60)
183
+ print("Example complete!")
184
+ print("=" * 60)
185
+
186
+
187
+ if __name__ == "__main__":
188
+ try:
189
+ main()
190
+ except Exception as e:
191
+ err = str(e)
192
+ print("\n" + "=" * 60)
193
+ if "OPENAI_API_KEY" in err or "api_key" in err.lower():
194
+ print("ERROR: OpenAI API key not configured")
195
+ print("Fix: export OPENAI_API_KEY=sk-...")
196
+ else:
197
+ print(f"ERROR: {e}")
198
+ print("=" * 60)
199
+ sys.exit(1)