@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,248 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Example: SecureMCP Calculator Server
4
+
5
+ Demonstrates the FastMCP-compatible SecureMCP API:
6
+ - @mcp.tool decorator for registering tools
7
+ - @mcp.resource decorator for read-only resources
8
+ - @mcp.prompt decorator for prompt templates
9
+ - Context object for vault access
10
+ - Context logging: ctx.debug(), ctx.info(), ctx.warning(), ctx.error()
11
+ - Context audit: ctx.audit() for signed audit entries
12
+ - Progress reporting: ctx.report_progress()
13
+ - mcp.run() for starting the server
14
+
15
+ This is the recommended way to build MCP servers with MACAW security.
16
+ """
17
+
18
+ import asyncio
19
+ from typing import List, Dict, Any
20
+
21
+ from macaw_adapters.mcp import SecureMCP, Context
22
+
23
+
24
+ # Create SecureMCP server - same pattern as FastMCP
25
+ mcp = SecureMCP("calculator", version="1.0.0")
26
+
27
+
28
+ # Tool: Add two numbers
29
+ @mcp.tool(description="Add two numbers together")
30
+ def add(a: float, b: float) -> float:
31
+ """Add a + b and return the result."""
32
+ return a + b
33
+
34
+
35
+ # Tool: Subtract two numbers
36
+ @mcp.tool(description="Subtract b from a")
37
+ def subtract(a: float, b: float) -> float:
38
+ """Subtract a - b and return the result."""
39
+ return a - b
40
+
41
+
42
+ # Tool: Multiply two numbers
43
+ @mcp.tool(description="Multiply two numbers")
44
+ def multiply(a: float, b: float) -> float:
45
+ """Multiply a * b and return the result."""
46
+ return a * b
47
+
48
+
49
+ # Tool: Divide two numbers
50
+ @mcp.tool(description="Divide a by b")
51
+ def divide(a: float, b: float) -> dict:
52
+ """Divide a / b and return the result."""
53
+ if b == 0:
54
+ return {"error": "Division by zero"}
55
+ return {"result": a / b}
56
+
57
+
58
+ # Tool with Context: Calculator with history tracking and logging
59
+ @mcp.tool(description="Calculate and track history")
60
+ def calculate(ctx: Context, operation: str, a: float, b: float) -> Dict[str, Any]:
61
+ """
62
+ Perform calculation and store in history.
63
+
64
+ Demonstrates Context usage for:
65
+ - Storing results in context vault (ctx.set)
66
+ - Retrieving previous values (ctx.get)
67
+ - Logging with ctx.info(), ctx.debug(), ctx.warning()
68
+ - Audit logging with ctx.audit()
69
+ """
70
+ ctx.info(f"Calculating: {a} {operation} {b}")
71
+
72
+ # Map operations to functions
73
+ ops = {
74
+ "add": lambda x, y: x + y,
75
+ "subtract": lambda x, y: x - y,
76
+ "multiply": lambda x, y: x * y,
77
+ "divide": lambda x, y: x / y if y != 0 else "error:div_by_zero"
78
+ }
79
+
80
+ if operation not in ops:
81
+ ctx.warning(f"Unknown operation requested: {operation}")
82
+ return {
83
+ "result": None,
84
+ "status": "invalid_operation",
85
+ "message": f"Unknown operation: {operation}"
86
+ }
87
+
88
+ # Check for division by zero
89
+ if operation == "divide" and b == 0:
90
+ ctx.warning("Division by zero attempted")
91
+
92
+ result = ops[operation](a, b)
93
+ ctx.debug(f"Result computed: {result}")
94
+
95
+ # Get existing history from context vault
96
+ history = ctx.get("calc_history") or []
97
+
98
+ # Add this calculation to history
99
+ entry = {
100
+ "operation": operation,
101
+ "a": a,
102
+ "b": b,
103
+ "result": result
104
+ }
105
+ history.append(entry)
106
+
107
+ # Store updated history in context vault
108
+ ctx.set("calc_history", history)
109
+
110
+ # Audit the calculation
111
+ ctx.audit(
112
+ action="calculation",
113
+ target="calculator",
114
+ outcome="success",
115
+ operation=operation,
116
+ operands=[a, b],
117
+ result=result
118
+ )
119
+
120
+ return {
121
+ "result": result,
122
+ "expression": f"{a} {operation} {b} = {result}",
123
+ "history_count": len(history)
124
+ }
125
+
126
+
127
+ # Tool with Progress: Batch calculations
128
+ @mcp.tool(description="Perform multiple calculations with progress")
129
+ async def batch_calculate(ctx: Context, calculations: list) -> Dict[str, Any]:
130
+ """
131
+ Perform multiple calculations, reporting progress.
132
+
133
+ Demonstrates:
134
+ - ctx.report_progress() for long-running operations
135
+ - Async tool execution
136
+
137
+ Args:
138
+ calculations: List of {"op": str, "a": float, "b": float}
139
+ """
140
+ total = len(calculations)
141
+ if total == 0:
142
+ return {"results": [], "count": 0}
143
+
144
+ ctx.info(f"Starting batch of {total} calculations")
145
+ await ctx.report_progress(0.0, f"Processing {total} calculations")
146
+
147
+ ops = {
148
+ "add": lambda x, y: x + y,
149
+ "subtract": lambda x, y: x - y,
150
+ "multiply": lambda x, y: x * y,
151
+ "divide": lambda x, y: x / y if y != 0 else "error:div_by_zero"
152
+ }
153
+
154
+ results = []
155
+ for i, calc in enumerate(calculations):
156
+ op = calc.get("op", "add")
157
+ a = calc.get("a", 0)
158
+ b = calc.get("b", 0)
159
+
160
+ progress = i / total
161
+ await ctx.report_progress(progress, f"Calculating {i+1}/{total}: {a} {op} {b}")
162
+
163
+ if op in ops:
164
+ result = ops[op](a, b)
165
+ results.append({"expression": f"{a} {op} {b}", "result": result})
166
+ else:
167
+ results.append({"expression": f"{a} {op} {b}", "error": "unknown op"})
168
+
169
+ await asyncio.sleep(0.1) # Simulate work
170
+
171
+ await ctx.report_progress(1.0, "Batch complete")
172
+ ctx.info(f"Batch complete: {total} calculations")
173
+
174
+ ctx.audit(
175
+ action="batch_calculation",
176
+ target="calculator",
177
+ outcome="success",
178
+ count=total
179
+ )
180
+
181
+ return {"results": results, "count": total}
182
+
183
+
184
+ # Resource: Get calculation history
185
+ @mcp.resource("calc://history", description="Get calculation history")
186
+ def get_history(ctx: Context) -> List[Dict[str, Any]]:
187
+ """
188
+ Read-only resource that returns calculation history.
189
+
190
+ Resources are treated as read-only tools in SecureMCP.
191
+ """
192
+ history = ctx.get("calc_history") or []
193
+ return {
194
+ "history": history,
195
+ "count": len(history)
196
+ }
197
+
198
+
199
+ # Prompt: Generate a calculation prompt
200
+ @mcp.prompt(description="Generate a prompt for calculations")
201
+ def calculation_prompt(numbers: str, operation: str = "add") -> str:
202
+ """
203
+ Generate a calculation prompt template.
204
+
205
+ Prompts are starting points for AuthenticatedPrompt creation.
206
+ """
207
+ return f"Please {operation} the following numbers: {numbers}"
208
+
209
+
210
+ if __name__ == "__main__":
211
+ import sys
212
+
213
+ # Optional transport for native (non-MACAW) MCP clients:
214
+ # python securemcp_calculator.py MACAW mesh only
215
+ # python securemcp_calculator.py stdio also serve Claude Desktop / Cursor
216
+ # python securemcp_calculator.py http also serve http://localhost:8080/mcp
217
+ transport = sys.argv[1] if len(sys.argv) > 1 else None
218
+
219
+ # stdout is the JSON-RPC stream when transport="stdio", so the banner goes to stderr.
220
+ out = sys.stderr
221
+
222
+ print("=" * 60, file=out)
223
+ print("SecureMCP Calculator Server", file=out)
224
+ print("=" * 60, file=out)
225
+ print(file=out)
226
+ print("This server provides:", file=out)
227
+ print(" Tools: add, subtract, multiply, divide, calculate, batch_calculate", file=out)
228
+ print(" Resources: calc://history", file=out)
229
+ print(" Prompts: calculation_prompt", file=out)
230
+ print(file=out)
231
+ print("Demonstrates:", file=out)
232
+ print(" - ctx.info/debug/warning/error() - Logging", file=out)
233
+ print(" - ctx.audit() - Signed audit entries", file=out)
234
+ print(" - ctx.report_progress() - Progress reporting", file=out)
235
+ print(" - ctx.get/set() - Context vault", file=out)
236
+ print(file=out)
237
+ print("All invocations are secured by MACAW:", file=out)
238
+ print(" - Cryptographic signing", file=out)
239
+ print(" - Policy enforcement", file=out)
240
+ print(" - Audit logging", file=out)
241
+ print(file=out)
242
+ if transport:
243
+ print(f"Also serving native MCP clients over: {transport}", file=out)
244
+ print("Starting server...", file=out)
245
+ print("=" * 60, file=out)
246
+
247
+ # Run the server
248
+ mcp.run(transport=transport)
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ SecureMCP Calculator Server
4
+
5
+ Run this first, then use 1a_simple_invocation.py to test.
6
+ """
7
+
8
+ from macaw_adapters.mcp import SecureMCP, Context
9
+
10
+ mcp = SecureMCP("calculator")
11
+
12
+
13
+ @mcp.tool(description="Add two numbers")
14
+ def add(a: float, b: float) -> float:
15
+ return a + b
16
+
17
+
18
+ @mcp.tool(description="Subtract two numbers")
19
+ def subtract(a: float, b: float) -> float:
20
+ return a - b
21
+
22
+
23
+ @mcp.tool(description="Multiply two numbers")
24
+ def multiply(a: float, b: float) -> float:
25
+ return a * b
26
+
27
+
28
+ @mcp.tool(description="Divide two numbers")
29
+ def divide(a: float, b: float) -> float:
30
+ if b == 0:
31
+ return float('inf')
32
+ return a / b
33
+
34
+
35
+ @mcp.tool(description="Calculate with history tracking")
36
+ def calculate(ctx: Context, op: str, a: float, b: float) -> dict:
37
+ """Perform calculation and track in history."""
38
+ ops = {
39
+ "add": lambda x, y: x + y,
40
+ "subtract": lambda x, y: x - y,
41
+ "multiply": lambda x, y: x * y,
42
+ "divide": lambda x, y: x / y if y != 0 else float('inf')
43
+ }
44
+
45
+ if op not in ops:
46
+ return {"error": f"Unknown operation: {op}"}
47
+
48
+ result = ops[op](a, b)
49
+
50
+ # Track in context vault
51
+ history = ctx.get("calc_history") or []
52
+ history.append({"op": op, "a": a, "b": b, "result": result})
53
+ ctx.set("calc_history", history)
54
+
55
+ return {"result": result, "history_count": len(history)}
56
+
57
+
58
+ @mcp.resource("calc://history")
59
+ def get_history(ctx: Context) -> dict:
60
+ """Get calculation history from context vault."""
61
+ history = ctx.get("calc_history") or []
62
+ return {"history": history, "count": len(history)}
63
+
64
+
65
+ if __name__ == "__main__":
66
+ print("=" * 50)
67
+ print("SecureMCP Calculator Server")
68
+ print("=" * 50)
69
+ print()
70
+ print("Tools: add, subtract, multiply, divide, calculate")
71
+ print("Resources: calc://history")
72
+ print()
73
+ print("Press Ctrl+C to stop")
74
+ print("=" * 50)
75
+ mcp.run()
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ openai_1a_dropin_simple.py - Drop-in replacement for OpenAI client
4
+
5
+ The simplest integration path - just replace your import and it works.
6
+ No Identity Provider needed.
7
+
8
+ Prerequisites:
9
+ - MACAW SDK installed (pip install macaw-client macaw-adapters)
10
+ - OPENAI_API_KEY environment variable
11
+
12
+ Run:
13
+ export OPENAI_API_KEY=sk-...
14
+ python openai_1a_dropin_simple.py
15
+ """
16
+
17
+ import os
18
+ import sys
19
+
20
+ # BEFORE: from openai import OpenAI
21
+ # AFTER:
22
+ from macaw_adapters.openai import SecureOpenAI
23
+
24
+ def main():
25
+ # Check for API key
26
+ if not os.environ.get("OPENAI_API_KEY"):
27
+ print("Set OPENAI_API_KEY environment variable")
28
+ print(" export OPENAI_API_KEY=sk-...")
29
+ return
30
+
31
+ print("=" * 60)
32
+ print("Example 1a: Drop-in Replacement (OpenAI)")
33
+ print("=" * 60)
34
+ print("\nPath: Direct on service (simplest)")
35
+ print("Use when: Single app, no user distinction, app-level policies")
36
+
37
+ # Create SecureOpenAI - just like regular OpenAI client
38
+ # Add intent_policy to restrict to GPT-3.5 only (no GPT-4)
39
+ client = SecureOpenAI(
40
+ app_name="my-simple-app",
41
+ intent_policy={
42
+ "constraints": {
43
+ "parameters": {
44
+ "tool:*/generate": {
45
+ "model": ["gpt-3.5-turbo"], # Only GPT-3.5 allowed
46
+ "max_tokens": {"max": 200}
47
+ }
48
+ }
49
+ }
50
+ }
51
+ )
52
+
53
+ print(f"\nService registered: {client.server_id}")
54
+ print(f"Mode: {client._mode}")
55
+ print("Policy: GPT-3.5 only, max 200 tokens")
56
+
57
+ # Same API as OpenAI - no changes needed
58
+ print("\n--- Test 1: Allowed model (gpt-3.5-turbo) ---")
59
+ response = client.chat.completions.create(
60
+ model="gpt-3.5-turbo",
61
+ max_tokens=100,
62
+ messages=[
63
+ {"role": "user", "content": "What is compound interest? Answer briefly."}
64
+ ]
65
+ )
66
+
67
+ print(f"Model: {response.model}")
68
+ print(f"Response: {response.choices[0].message.content}")
69
+
70
+ # Policy enforcement - try a blocked model
71
+ print("\n--- Test 2: Blocked model (gpt-4) ---")
72
+ try:
73
+ response = client.chat.completions.create(
74
+ model="gpt-4", # NOT in allowed list!
75
+ max_tokens=100,
76
+ messages=[
77
+ {"role": "user", "content": "What is compound interest?"}
78
+ ]
79
+ )
80
+ print(f"Unexpected success - should have been blocked!")
81
+ print(f"Response: {response.choices[0].message.content[:100]}...")
82
+ except Exception as e:
83
+ print(f"Correctly blocked: {e}")
84
+
85
+ print("\n" + "=" * 60)
86
+ print("Example complete!")
87
+ print("=" * 60)
88
+
89
+
90
+ if __name__ == "__main__":
91
+ try:
92
+ main()
93
+ except Exception as e:
94
+ err = str(e)
95
+ print("\n" + "=" * 60)
96
+ if "OPENAI_API_KEY" in err or "api_key" in err.lower():
97
+ print("ERROR: OpenAI API key not configured")
98
+ print("Fix: export OPENAI_API_KEY=sk-...")
99
+ else:
100
+ print(f"ERROR: {e}")
101
+ print("=" * 60)
102
+ sys.exit(1)
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ openai_1b_multiuser_bind.py - Multi-user OpenAI 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
+ - OPENAI_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 OPENAI_API_KEY=sk-...
16
+ python openai_1b_multiuser_bind.py
17
+
18
+ No IdP configured? Run simpler example first:
19
+ python openai_1a_dropin_simple.py
20
+ """
21
+
22
+ import os
23
+ import sys
24
+
25
+ from macaw_adapters.openai import SecureOpenAI
26
+ from macaw_client import MACAWClient, RemoteIdentityProvider
27
+
28
+
29
+ # Test configurations based on user policies
30
+ # Alice: GPT-3.5 only, max 500 tokens
31
+ # Bob: GPT-3.5/4, max 2000 tokens
32
+ USER_TESTS = {
33
+ "alice": {
34
+ "password": "Alice123!",
35
+ "policy_desc": "GPT-3.5 only, max 500 tokens",
36
+ "tests": [
37
+ # (model, max_tokens, should_succeed)
38
+ ("gpt-3.5-turbo", 400, True), # ALLOWED - correct model, within limit
39
+ ("gpt-4", 400, False), # BLOCKED - alice can't use GPT-4
40
+ ("gpt-3.5-turbo", 600, False), # BLOCKED - exceeds max_tokens
41
+ ]
42
+ },
43
+ "bob": {
44
+ "password": "Bob@123!",
45
+ "policy_desc": "GPT-3.5/4, max 2000 tokens",
46
+ "tests": [
47
+ ("gpt-3.5-turbo", 400, True), # ALLOWED
48
+ ("gpt-4", 400, True), # ALLOWED - bob CAN use GPT-4
49
+ ("gpt-3.5-turbo", 600, True), # ALLOWED - within bob's 2000 limit
50
+ ("gpt-4", 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, openai_service: SecureOpenAI):
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: {openai_service.server_id}")
89
+ user_openai = openai_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_openai.chat.completions.create(
99
+ model=model,
100
+ max_tokens=max_tokens,
101
+ messages=[
102
+ {"role": "system", "content": "You are a financial analyst."},
103
+ {"role": "user", "content": "What is revenue growth? Brief answer."}
104
+ ]
105
+ )
106
+
107
+ if should_succeed:
108
+ print(f" SUCCESS (as expected)")
109
+ # Non-streaming: response is a ChatCompletion object
110
+ content = response.choices[0].message.content
111
+ print(f" Model: {response.model}")
112
+ print(f" Response: {content[:60]}...")
113
+ else:
114
+ print(f" UNEXPECTED SUCCESS - should have been blocked!")
115
+
116
+ except Exception as e:
117
+ error = str(e)
118
+ is_policy_block = any(word in error.lower() for word in
119
+ ["not in allowed", "not permitted", "policy",
120
+ "max_tokens", "model", "blocked", "denied"])
121
+ if is_policy_block:
122
+ if should_succeed:
123
+ print(f" UNEXPECTED BLOCK: {error[:60]}...")
124
+ else:
125
+ print(f" CORRECTLY BLOCKED by policy")
126
+ else:
127
+ print(f" ERROR: {error[:60]}...")
128
+
129
+ # 4. Cleanup
130
+ print(f"\n Unbinding user...")
131
+ user_openai.unbind()
132
+ print(f" is_bound: {user_openai.is_bound}")
133
+
134
+
135
+ def main():
136
+ # Check for API key
137
+ if not os.environ.get("OPENAI_API_KEY"):
138
+ print("Set OPENAI_API_KEY environment variable")
139
+ print(" export OPENAI_API_KEY=sk-...")
140
+ return
141
+
142
+ print("=" * 60)
143
+ print("Example 1b: Multi-user with bind_to_user (OpenAI)")
144
+ print("=" * 60)
145
+ print("\nPath: bind_to_user (per-user identity)")
146
+ print("Use when: SaaS app, different users need different permissions")
147
+ print("\nArchitecture:")
148
+ print(" - Single SecureOpenAI service (shared)")
149
+ print(" - Per-user MACAWClient with JWT identity")
150
+ print(" - bind_to_user() connects user to service")
151
+ print(" - User's identity flows through for policy evaluation")
152
+
153
+ # Create SINGLE service (shared across all users)
154
+ print("\n--- Creating SecureOpenAI service ---")
155
+ openai_service = SecureOpenAI(app_name="openai-service")
156
+ print(f"Service registered: {openai_service.server_id}")
157
+ print(f"Mode: {openai_service._mode}")
158
+
159
+ # Test alice (restricted)
160
+ try:
161
+ test_user("alice", openai_service)
162
+ except Exception as e:
163
+ print(f"\nFailed to test alice: {e}")
164
+
165
+ # Test bob (enhanced)
166
+ try:
167
+ test_user("bob", openai_service)
168
+ except Exception as e:
169
+ print(f"\nFailed to test bob: {e}")
170
+
171
+ print("\n" + "=" * 60)
172
+ print("Example complete!")
173
+ print("=" * 60)
174
+
175
+
176
+ if __name__ == "__main__":
177
+ try:
178
+ main()
179
+ except Exception as e:
180
+ err = str(e)
181
+ print("\n" + "=" * 60)
182
+ if "OPENAI_API_KEY" in err or "api_key" in err.lower():
183
+ print("ERROR: OpenAI API key not configured")
184
+ print("Fix: export OPENAI_API_KEY=sk-...")
185
+ elif "Local provider does not support" in err:
186
+ print("ERROR: Identity Provider not configured")
187
+ print("Fix: Console -> Settings -> Identity Bridge")
188
+ print("\nOr run simpler example first:")
189
+ print(" python openai_1a_dropin_simple.py")
190
+ else:
191
+ print(f"ERROR: {e}")
192
+ print("=" * 60)
193
+ sys.exit(1)