@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,180 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 1a_login_alice.py - Phishing-Resistance (login-strength gating)
4
+
5
+ The simplest phishing-resistance example: a tool that only runs if the
6
+ human behind the agent logged in with a phishing-resistant method
7
+ (passkey / hardware key / WebAuthn).
8
+
9
+ There is no human in the loop and no waiting. When the user authenticates,
10
+ their token carries an `amr` (authentication methods) claim describing how
11
+ they logged in — `fido`/`hwk` for a passkey, `pwd` for a password. The
12
+ control plane reads it at LOGIN and, if the login was phishing-resistant,
13
+ mints an internal `phishing_resistant` grant for the agent. The Policy
14
+ Enforcement Point then allows or denies with no prompt.
15
+
16
+ Run it TWICE, changing only how you log in (via get_token.py):
17
+ PASSKEY -> amr:["fido"] -> phishing-resistant -> ALLOWED
18
+ PASSWORD -> amr:["pwd","mfa"] -> not -> DENIED
19
+
20
+ That single variable — the login method — is the whole lesson.
21
+
22
+ NOTE: no approver is involved here. 1b_trade_alice.py shows the same gate
23
+ used as a precondition in front of a human approval step.
24
+
25
+ Prerequisites:
26
+ - MACAW control plane running (LocalAgent)
27
+ - Entra (Azure AD) connector configured in Console -> Settings -> Identity Bridge
28
+ - An Entra user with a passkey enrolled (for the ALLOW run)
29
+ - A token minted by get_token.py (sets ~/.macaw_demo_token / ALICE_TOKEN)
30
+
31
+ Run:
32
+ python get_token.py # sign in: passkey (ALLOW) or password (DENY)
33
+ export ALICE_TOKEN=$(cat ~/.macaw_demo_token)
34
+ python 1a_login_alice.py
35
+ """
36
+
37
+ import json
38
+ import os
39
+
40
+ from macaw_client import MACAWClient
41
+
42
+
43
+ # Any amount — there is no threshold here; phishing_resistant is unconditional.
44
+ TRADE_SYMBOL = "AAPL"
45
+ TRADE_AMOUNT = 500
46
+
47
+
48
+ def execute_trade_handler(params):
49
+ """Execute trade - the actual tool implementation."""
50
+ print(f"\n [TOOL] execute_trade invoked!")
51
+ print(f" - symbol: {params.get('symbol')}")
52
+ print(f" - amount: ${params.get('amount'):,}")
53
+ print(f" - action: {params.get('action')}")
54
+ return {
55
+ "status": "executed",
56
+ "symbol": params.get("symbol"),
57
+ "amount": params.get("amount"),
58
+ "action": params.get("action"),
59
+ "trade_id": "TRD-00001",
60
+ "message": f"Successfully {params.get('action')} ${params.get('amount'):,} of {params.get('symbol')}",
61
+ }
62
+
63
+
64
+ def main():
65
+ print("=" * 60)
66
+ print("Example 1a: Phishing-Resistance - Login Gating (Alice)")
67
+ print("=" * 60)
68
+
69
+ # Step 1: Create the Trading Service (provides execute_trade)
70
+ print("\n[Step 1] Creating Trading Service...")
71
+ try:
72
+ trading_service = MACAWClient(
73
+ app_name="trading",
74
+ agent_type="service",
75
+ tools={
76
+ "tool:trading/execute_trade": {
77
+ "handler": execute_trade_handler,
78
+ "description": "Execute a stock trade",
79
+ }
80
+ },
81
+ )
82
+ if not trading_service.register():
83
+ print(" ERROR: Failed to register Trading Service")
84
+ print(" Make sure the control plane is running: python3 -m macaw_agent.main")
85
+ return 1
86
+ print(f" Service ID: {trading_service.agent_id}")
87
+ except Exception as e:
88
+ print(f" ERROR: Failed to create Trading Service: {e}")
89
+ return 1
90
+
91
+ # Step 2: Authenticate as Alice (token from get_token.py)
92
+ print("\n[Step 2] Authenticating as alice...")
93
+ # passkey login -> amr:fido -> phishing-resistant (ALLOW run)
94
+ # password login -> amr:pwd/mfa -> not (DENY run)
95
+ jwt_token = os.environ.get("ALICE_TOKEN")
96
+ if not jwt_token and os.path.exists(os.path.expanduser("~/.macaw_demo_token")):
97
+ jwt_token = open(os.path.expanduser("~/.macaw_demo_token")).read().strip()
98
+ if not jwt_token:
99
+ print(" ERROR: no ALICE_TOKEN — run get_token.py first")
100
+ trading_service.unregister()
101
+ return 1
102
+ print(" Got JWT token")
103
+
104
+ # Step 3: Create Alice's user agent — gated ONLY by phishing_resistant
105
+ print("\n[Step 3] Creating Alice's user agent...")
106
+ try:
107
+ alice = MACAWClient(
108
+ user_name="alice",
109
+ iam_token=jwt_token,
110
+ agent_type="user",
111
+ app_name="trading-app",
112
+ intent_policy={
113
+ "resources": ["tool:trading/execute_trade"],
114
+ # The one and only gate: the human must have logged in with a
115
+ # phishing-resistant method. Deny-if-absent — no approver, no wait.
116
+ "attestations": ["phishing_resistant"],
117
+ },
118
+ )
119
+ if not alice.register():
120
+ print(" ERROR: Failed to register Alice's agent")
121
+ trading_service.unregister()
122
+ return 1
123
+ print(f" Agent ID: {alice.agent_id}")
124
+ except Exception as e:
125
+ print(f" ERROR: Failed to create Alice's agent: {e}")
126
+ trading_service.unregister()
127
+ return 1
128
+
129
+ # Step 4: Invoke the gated tool — allowed iff the login was phishing-resistant
130
+ print("\n[Step 4] Invoking gated tool (no approver, no waiting)...")
131
+ print(" Gate: attestation 'phishing_resistant' must be present for alice@trading-app")
132
+ print("-" * 60)
133
+ try:
134
+ result = alice.invoke_tool(
135
+ tool_name="tool:trading/execute_trade",
136
+ target_agent=trading_service.agent_id,
137
+ parameters={"symbol": TRADE_SYMBOL, "amount": TRADE_AMOUNT, "action": "buy"},
138
+ )
139
+ print("\n" + "=" * 60)
140
+ print("ALLOWED — login was phishing-resistant (amr:fido)")
141
+ print("=" * 60)
142
+ print(f"Result: {json.dumps(result, indent=2)}")
143
+ except Exception as e:
144
+ error_msg = str(e)
145
+ if "phishing_resistant" in error_msg or "attestation" in error_msg.lower() or "denied" in error_msg.lower():
146
+ print("\n" + "=" * 60)
147
+ print("DENIED — login was NOT phishing-resistant")
148
+ print("=" * 60)
149
+ print(f"\nReason: {error_msg}")
150
+ print("\nRe-run get_token.py and sign in with a PASSKEY to get the ALLOW path.")
151
+ else:
152
+ print(f"\n ERROR: {e}")
153
+ finally:
154
+ print("\n[Cleanup] Unregistering agents...")
155
+ try:
156
+ alice.unregister()
157
+ trading_service.unregister()
158
+ except Exception:
159
+ pass
160
+
161
+ return 0
162
+
163
+
164
+ if __name__ == "__main__":
165
+ import sys
166
+ try:
167
+ sys.exit(main())
168
+ except Exception as e:
169
+ err = str(e)
170
+ print("\n" + "=" * 60)
171
+ if "Local provider does not support" in err:
172
+ print("ERROR: Identity Provider not configured")
173
+ print("Fix: Console -> Settings -> Identity Bridge (add the Entra connector)")
174
+ elif "Connection refused" in err or "connect" in err.lower():
175
+ print("ERROR: Cannot connect to MACAW")
176
+ print("Fix: Ensure the control plane (LocalAgent) is running")
177
+ else:
178
+ print(f"ERROR: {e}")
179
+ print("=" * 60)
180
+ sys.exit(1)
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 1b_trade_alice.py - Phishing-Resistance as a precondition to human approval
4
+
5
+ This builds on the alice/bob trade example (examples/attestations/) by
6
+ LAYERING two gates on the same trade:
7
+
8
+ 1. phishing_resistant - internal, decided at login from the token's amr.
9
+ Deny-if-absent, no approver, checked FIRST.
10
+ 2. trade-approved - external HITL, blocks waiting for a manager (bob),
11
+ only required when amount > $10,000.
12
+
13
+ Two run outcomes, driven only by how Alice logged in (via get_token.py):
14
+
15
+ PASSKEY -> clears phishing_resistant -> BLOCKS on trade-approved
16
+ -> bob approves (1b_trade_bob.py) -> trade executes.
17
+ PASSWORD -> DENIED at phishing_resistant. The trade never reaches the
18
+ manager gate; bob is never involved.
19
+
20
+ Start simple with 1a_login_alice.py (phishing-resistance alone); this
21
+ example shows how it composes with a human approval step.
22
+
23
+ NOTE: interactive — the passkey run needs bob to approve. Not for
24
+ automated test harnesses.
25
+
26
+ Prerequisites:
27
+ - MACAW control plane running (LocalAgent)
28
+ - Entra connector configured (Console -> Settings -> Identity Bridge),
29
+ an Entra user with a passkey, and a token from get_token.py
30
+ - bob configured as an approver with role:manager (see attestations/ demo)
31
+
32
+ Run:
33
+ python get_token.py # sign in with a PASSKEY
34
+ export ALICE_TOKEN=$(cat ~/.macaw_demo_token)
35
+
36
+ # Terminal 1: Alice's trade (clears phishing gate, then blocks on approval)
37
+ python 1b_trade_alice.py
38
+
39
+ # Terminal 2: Bob approves
40
+ python 1b_trade_bob.py
41
+ """
42
+
43
+ import json
44
+ import os
45
+
46
+ from macaw_client import MACAWClient, RemoteIdentityProvider
47
+
48
+
49
+ # Trade configuration
50
+ TRADE_SYMBOL = "AAPL"
51
+ TRADE_AMOUNT = 15000 # High-value trade requiring approval
52
+
53
+
54
+ def execute_trade_handler(params):
55
+ """Execute trade - the actual tool implementation."""
56
+ print(f"\n [TOOL] execute_trade invoked!")
57
+ print(f" [TOOL] Received parameters:")
58
+ print(f" - symbol: {params.get('symbol')}")
59
+ print(f" - amount: ${params.get('amount'):,}")
60
+ print(f" - action: {params.get('action')}")
61
+
62
+ # Simulate trade execution
63
+ result = {
64
+ "status": "executed",
65
+ "symbol": params.get("symbol"),
66
+ "amount": params.get("amount"),
67
+ "action": params.get("action"),
68
+ "trade_id": "TRD-12345",
69
+ "message": f"Successfully {params.get('action')} ${params.get('amount'):,} of {params.get('symbol')}"
70
+ }
71
+ print(f" [TOOL] Returning: {result}")
72
+ return result
73
+
74
+
75
+ def main():
76
+ print("=" * 60)
77
+ print("Example 1b: Phishing-Resistance + Trade Approval (Alice)")
78
+ print("=" * 60)
79
+
80
+ # Step 1: Create Trading Service (provides execute_trade tool)
81
+ print("\n[Step 1] Creating Trading Service...")
82
+
83
+ try:
84
+ trading_service = MACAWClient(
85
+ app_name="trading",
86
+ agent_type="service",
87
+ tools={
88
+ "tool:trading/execute_trade": {
89
+ "handler": execute_trade_handler,
90
+ "description": "Execute a stock trade"
91
+ }
92
+ }
93
+ )
94
+
95
+ if not trading_service.register():
96
+ print(" ERROR: Failed to register Trading Service")
97
+ print("\n Make sure LocalAgent is running:")
98
+ print(" python3 -m macaw_agent.main")
99
+ return 1
100
+
101
+ print(f" Service ID: {trading_service.agent_id}")
102
+ print(f" Provides: tool:trading/execute_trade")
103
+
104
+ except Exception as e:
105
+ print(f" ERROR: Failed to create Trading Service: {e}")
106
+ return 1
107
+
108
+ # Step 2: Authenticate as Alice
109
+ print("\n[Step 2] Authenticating as alice...")
110
+ try:
111
+ # Alice authenticates via Entra. Token is minted by get_token.py:
112
+ # passkey login -> amr:fido -> phishing-resistant (ALLOW path)
113
+ # password login -> amr:pwd/mfa -> not (DENY path)
114
+ jwt_token = os.environ.get("ALICE_TOKEN")
115
+ if not jwt_token and os.path.exists(os.path.expanduser("~/.macaw_demo_token")):
116
+ jwt_token = open(os.path.expanduser("~/.macaw_demo_token")).read().strip()
117
+ if not jwt_token:
118
+ print(" ERROR: no ALICE_TOKEN — run get_token.py first")
119
+ return 1
120
+ print(" Got JWT token")
121
+ except Exception as e:
122
+ print(f" ERROR: Failed to authenticate: {e}")
123
+ print("\n Make sure your identity provider is running and alice user exists")
124
+ trading_service.unregister()
125
+ return 1
126
+
127
+ # Step 3: Create Alice's user agent with attestation policy
128
+ print("\n[Step 3] Creating Alice's user agent...")
129
+ try:
130
+ # MAPL v3 format for attestations:
131
+ # - "attestations" array: defines WHEN attestation is required
132
+ # - "constraints.attestations": defines HOW attestation works
133
+ alice = MACAWClient(
134
+ user_name="alice",
135
+ iam_token=jwt_token,
136
+ agent_type="user",
137
+ app_name="trading-app",
138
+ intent_policy={
139
+ "resources": ["tool:trading/execute_trade"],
140
+ # Attestation required when amount > 10000
141
+ "attestations": [
142
+ # Precondition: the human must have logged in with a phishing-resistant
143
+ # method (hardware key / passkey). Deny-if-absent, checked before approval.
144
+ "phishing_resistant",
145
+ "trade-approved::{params.amount > 10000}"
146
+ ],
147
+ # Attestation metadata
148
+ "constraints": {
149
+ "attestations": {
150
+ "trade-approved": {
151
+ "approval_criteria": "role:manager",
152
+ "timeout": 300, # 5 minutes to approve
153
+ "time_to_live": 3600, # Valid for 1 hour after approval
154
+ "one_time": True # Consumed after single use
155
+ }
156
+ }
157
+ }
158
+ }
159
+ )
160
+
161
+ if not alice.register():
162
+ print(" ERROR: Failed to register Alice's agent")
163
+ trading_service.unregister()
164
+ return 1
165
+
166
+ print(f" Agent ID: {alice.agent_id}")
167
+
168
+ except Exception as e:
169
+ print(f" ERROR: Failed to create Alice's agent: {e}")
170
+ trading_service.unregister()
171
+ return 1
172
+
173
+ # Step 4: Execute trade (will block waiting for approval)
174
+ print("\n[Step 4] Executing trade...")
175
+ print(f" Symbol: {TRADE_SYMBOL}")
176
+ print(f" Amount: ${TRADE_AMOUNT:,}")
177
+ print(f"\n Policy check: amount ({TRADE_AMOUNT}) > 10000 -> attestation REQUIRED")
178
+ print(f" Attestation: 'trade-approved' requires role:manager")
179
+ print(f"\n Creating PENDING attestation and BLOCKING...")
180
+ print(f" Bob has 5 minutes (timeout=300s) to approve.")
181
+ print(f"\n >>> Run 1b_trade_bob.py in another terminal <<<")
182
+ print("-" * 60)
183
+
184
+ try:
185
+ # This will block internally while waiting for attestation
186
+ result = alice.invoke_tool(
187
+ tool_name="tool:trading/execute_trade",
188
+ target_agent=trading_service.agent_id,
189
+ parameters={
190
+ "symbol": TRADE_SYMBOL,
191
+ "amount": TRADE_AMOUNT,
192
+ "action": "buy"
193
+ }
194
+ )
195
+
196
+ print("\n" + "=" * 60)
197
+ print("TRADE EXECUTED SUCCESSFULLY!")
198
+ print("=" * 60)
199
+ print(f"Result: {json.dumps(result, indent=2)}")
200
+
201
+ except Exception as e:
202
+ error_msg = str(e)
203
+ if "attestation" in error_msg.lower() or "denied" in error_msg.lower():
204
+ print("\n" + "=" * 60)
205
+ print("TRADE BLOCKED - ATTESTATION REQUIRED OR DENIED")
206
+ print("=" * 60)
207
+ print(f"\nError: {error_msg}")
208
+ print(f"\nTo approve, Bob should run:")
209
+ print(f" python 1b_trade_bob.py")
210
+ else:
211
+ print(f"\n ERROR: {e}")
212
+
213
+ finally:
214
+ # Cleanup
215
+ print("\n[Cleanup] Unregistering agents...")
216
+ try:
217
+ alice.unregister()
218
+ trading_service.unregister()
219
+ except:
220
+ pass
221
+
222
+ return 0
223
+
224
+
225
+ if __name__ == "__main__":
226
+ import sys
227
+ try:
228
+ sys.exit(main())
229
+ except Exception as e:
230
+ err = str(e)
231
+ print("\n" + "=" * 60)
232
+ if "Local provider does not support" in err:
233
+ print("ERROR: Identity Provider not configured")
234
+ print("Fix: Console -> Settings -> Identity Bridge")
235
+ elif "Connection refused" in err or "connect" in err.lower():
236
+ print("ERROR: Cannot connect to MACAW")
237
+ print("Fix: Ensure LocalAgent is running")
238
+ else:
239
+ print(f"ERROR: {e}")
240
+ print("=" * 60)
241
+ sys.exit(1)
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 1b_trade_bob.py - External Attestation Manager Approval
4
+
5
+ Bob reviews and approves/denies pending attestation requests.
6
+ Alice's trade request is BLOCKING, waiting for Bob's approval.
7
+
8
+ This demonstrates:
9
+ - list_attestations(): Find pending requests you can approve
10
+ - approve_attestation(): Grant the request
11
+ - deny_attestation(): Reject the request with reason
12
+
13
+ NOTE: This example requires interactive input and is not suitable
14
+ for automated test harnesses.
15
+
16
+ Prerequisites:
17
+ - MACAW SDK installed (pip install macaw-client macaw-adapters)
18
+ - Identity Provider configured (Console -> Settings -> Identity Bridge)
19
+ - Test users: alice/Alice123!, bob/Bob@123! (bob needs manager role)
20
+ - Alice has already requested a trade (1b_trade_alice.py running)
21
+
22
+ Run:
23
+ # Terminal 1: Run Alice's trade request first
24
+ python 1b_trade_alice.py
25
+
26
+ # Terminal 2: Run Bob's approval
27
+ python 1b_trade_bob.py
28
+ """
29
+
30
+ import json
31
+
32
+ from macaw_client import MACAWClient, RemoteIdentityProvider
33
+
34
+
35
+ def main():
36
+ print("=" * 60)
37
+ print("Example 1b: External Attestation - Manager Approval (Bob)")
38
+ print("=" * 60)
39
+
40
+ # Step 1: Authenticate as Bob
41
+ print("\n[Step 1] Authenticating as bob...")
42
+ try:
43
+ jwt_token, _ = RemoteIdentityProvider().login("bob", "Bob@123!")
44
+ print(" Got JWT token")
45
+ except Exception as e:
46
+ print(f" ERROR: Failed to authenticate: {e}")
47
+ print("\n Make sure your identity provider is running and bob user exists")
48
+ return 1
49
+
50
+ # Step 2: Create Bob's user agent
51
+ print("\n[Step 2] Creating Bob's user agent...")
52
+ try:
53
+ bob = MACAWClient(
54
+ user_name="bob",
55
+ iam_token=jwt_token,
56
+ agent_type="user",
57
+ app_name="attestation-manager",
58
+ intent_policy={
59
+ "resources": ["attestation:*"],
60
+ "constraints": {
61
+ "roles": ["manager"]
62
+ }
63
+ }
64
+ )
65
+
66
+ if not bob.register():
67
+ print(" ERROR: Failed to register Bob's agent")
68
+ print("\n Make sure LocalAgent is running:")
69
+ print(" python3 -m macaw_agent.main")
70
+ return 1
71
+
72
+ print(f" Agent ID: {bob.agent_id}")
73
+
74
+ except Exception as e:
75
+ print(f" ERROR: Failed to create Bob's agent: {e}")
76
+ return 1
77
+
78
+ # Step 3: List pending attestations
79
+ print("\n[Step 3] Listing pending attestations...")
80
+ try:
81
+ attestations = bob.list_attestations(status="pending")
82
+ except Exception as e:
83
+ print(f" ERROR: Failed to list attestations: {e}")
84
+ bob.unregister()
85
+ return 1
86
+
87
+ if not attestations:
88
+ print(" No pending attestations found.")
89
+ print("\n Make sure Alice has requested a trade first:")
90
+ print(" python 1b_trade_alice.py")
91
+ bob.unregister()
92
+ return 0
93
+
94
+ print(f"\n Found {len(attestations)} pending attestation(s):\n")
95
+
96
+ for i, att in enumerate(attestations, 1):
97
+ print(f" [{i}] Key: {att.get('key')}")
98
+ print(f" For Agent: {att.get('for_agent')}")
99
+ print(f" Approval Criteria: {att.get('approval_criteria')}")
100
+ print(f" One-Time: {att.get('one_time', False)}")
101
+ if att.get('value'):
102
+ print(f" Value: {json.dumps(att.get('value', {}))}")
103
+ print()
104
+
105
+ # Step 4: Approve/Deny attestation(s)
106
+ print("[Step 4] Process attestations...")
107
+ print(" Options: [y] Approve [d] Deny [s] Skip")
108
+
109
+ for i, att in enumerate(attestations, 1):
110
+ key = att.get('key')
111
+ for_agent = att.get('for_agent')
112
+
113
+ response = input(f"\n '{key}' for {for_agent}? [y/d/s]: ").strip().lower()
114
+
115
+ if response == 'y':
116
+ print(f" Approving...")
117
+ try:
118
+ if bob.approve_attestation(att, reason="Manager approved"):
119
+ print(f" APPROVED - Alice's request will proceed")
120
+ else:
121
+ print(f" Failed to approve")
122
+ except Exception as e:
123
+ print(f" ERROR: {e}")
124
+
125
+ elif response == 'd':
126
+ print(f" Denying...")
127
+ try:
128
+ if bob.deny_attestation(att, reason="Manager denied"):
129
+ print(f" DENIED - Alice's request will be rejected")
130
+ else:
131
+ print(f" Failed to deny")
132
+ except Exception as e:
133
+ print(f" ERROR: {e}")
134
+
135
+ else:
136
+ print(f" Skipped")
137
+
138
+ # Cleanup
139
+ bob.unregister()
140
+
141
+ print("\n" + "=" * 60)
142
+ print("Done. Alice's request should now proceed (or fail if denied).")
143
+ print("=" * 60)
144
+
145
+ return 0
146
+
147
+
148
+ if __name__ == "__main__":
149
+ import sys
150
+ try:
151
+ sys.exit(main())
152
+ except Exception as e:
153
+ err = str(e)
154
+ print("\n" + "=" * 60)
155
+ if "Local provider does not support" in err:
156
+ print("ERROR: Identity Provider not configured")
157
+ print("Fix: Console -> Settings -> Identity Bridge")
158
+ elif "Connection refused" in err or "connect" in err.lower():
159
+ print("ERROR: Cannot connect to MACAW")
160
+ print("Fix: Ensure LocalAgent is running")
161
+ else:
162
+ print(f"ERROR: {e}")
163
+ print("=" * 60)
164
+ sys.exit(1)