@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,115 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 1a_simple_invocation.py - Simple MCP Tool Invocation
4
+
5
+ Demonstrates:
6
+ - Auto-discovering SecureMCP servers
7
+ - Invoking tools on a server
8
+ - Basic tool calls (add, subtract, multiply, divide)
9
+ - Tool with context (calculate with history)
10
+
11
+ Prerequisites:
12
+ - MACAW SDK installed (pip install macaw-client macaw-adapters)
13
+ - Calculator server running (securemcp_calculator.py)
14
+
15
+ Run:
16
+ # Terminal 1: Start the server
17
+ python securemcp_calculator.py
18
+
19
+ # Terminal 2: Run this client
20
+ python 1a_simple_invocation.py
21
+ """
22
+
23
+ from macaw_adapters.mcp import Client
24
+
25
+
26
+ def find_calculator_server(client):
27
+ """Find the most recent securemcp-calculator server."""
28
+ agents = client.macaw_client.list_agents(agent_type="app")
29
+ # Match exactly "/app:securemcp-calculator:" to avoid stale registrations
30
+ calc_servers = [
31
+ a for a in agents
32
+ if "/app:securemcp-calculator:" in a.get("agent_id", "")
33
+ and "/tool." not in a.get("agent_id", "")
34
+ ]
35
+
36
+ if not calc_servers:
37
+ print("No calculator server found!")
38
+ print("\nStart the calculator server first:")
39
+ print(" python3 securemcp_calculator.py")
40
+ return None
41
+
42
+ return calc_servers[0]["agent_id"]
43
+
44
+
45
+ def main():
46
+ print("=" * 50)
47
+ print("Example 1a: Simple Tool Invocation")
48
+ print("=" * 50)
49
+
50
+ # Create client (auto-registers with LocalAgent)
51
+ client = Client("example-1a")
52
+ print(f"Client: {client.client_id}")
53
+
54
+ # Find calculator server
55
+ server_id = find_calculator_server(client)
56
+ if not server_id:
57
+ return
58
+
59
+ print(f"Server: {server_id}")
60
+ print()
61
+
62
+ # Test basic tools
63
+ print("Basic Tool Invocations:")
64
+ print("-" * 40)
65
+
66
+ tests = [
67
+ ("add", {"a": 10, "b": 5}),
68
+ ("subtract", {"a": 100, "b": 42}),
69
+ ("multiply", {"a": 7, "b": 8}),
70
+ ("divide", {"a": 100, "b": 4}),
71
+ ]
72
+
73
+ for tool_name, args in tests:
74
+ result = client.macaw_client.invoke_tool(
75
+ tool_name,
76
+ args,
77
+ target_agent=server_id
78
+ )
79
+ args_str = ", ".join(f"{k}={v}" for k, v in args.items())
80
+ print(f" {tool_name}({args_str}) = {result}")
81
+
82
+ print()
83
+
84
+ # Test tool with context
85
+ print("Tool with Context (history tracking):")
86
+ print("-" * 40)
87
+
88
+ for op in ["add", "multiply", "subtract"]:
89
+ result = client.macaw_client.invoke_tool(
90
+ "calculate",
91
+ {"operation": op, "a": 10, "b": 3},
92
+ target_agent=server_id
93
+ )
94
+ print(f" calculate({op}, 10, 3) = {result}")
95
+
96
+ print()
97
+ print("=" * 50)
98
+ print("Done!")
99
+ print("=" * 50)
100
+
101
+
102
+ if __name__ == "__main__":
103
+ try:
104
+ main()
105
+ except Exception as e:
106
+ err = str(e)
107
+ print("\n" + "=" * 50)
108
+ if "Connection refused" in err or "connect" in err.lower():
109
+ print("ERROR: Cannot connect to MACAW")
110
+ print("Fix: Ensure MACAW is running")
111
+ else:
112
+ print(f"ERROR: {e}")
113
+ print("=" * 50)
114
+ import sys
115
+ sys.exit(1)
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 1b_discovery_and_resources.py - MCP Discovery and Resources
4
+
5
+ Demonstrates:
6
+ - list_tools() - discover available tools
7
+ - list_resources() - discover available resources
8
+ - list_prompts() - discover available prompts
9
+ - get_resource() - read a resource
10
+ - get_prompt() - get a prompt template
11
+ - Filtering by server name
12
+
13
+ Prerequisites:
14
+ - MACAW SDK installed (pip install macaw-client macaw-adapters)
15
+ - Calculator server running (securemcp_calculator.py)
16
+
17
+ Run:
18
+ # Terminal 1: Start the server
19
+ python securemcp_calculator.py
20
+
21
+ # Terminal 2: Run this client
22
+ python 1b_discovery_and_resources.py
23
+ """
24
+
25
+ import asyncio
26
+
27
+ from macaw_adapters.mcp import Client
28
+
29
+
30
+ def find_calculator_server(client):
31
+ """Find the most recent securemcp-calculator server."""
32
+ agents = client.macaw_client.list_agents(agent_type="app")
33
+ # Match exactly "/app:securemcp-calculator:" to avoid stale registrations
34
+ calc_servers = [
35
+ a for a in agents
36
+ if "/app:securemcp-calculator:" in a.get("agent_id", "")
37
+ and "/tool." not in a.get("agent_id", "")
38
+ ]
39
+
40
+ if not calc_servers:
41
+ print("No calculator server found!")
42
+ print("\nStart the calculator server first:")
43
+ print(" python3 securemcp_calculator.py")
44
+ return None
45
+
46
+ return calc_servers[0]["agent_id"]
47
+
48
+
49
+ async def main():
50
+ print("=" * 50)
51
+ print("Example 1b: Discovery and Resources")
52
+ print("=" * 50)
53
+
54
+ # Create client
55
+ client = Client("example-1b")
56
+ print(f"Client: {client.client_id}")
57
+
58
+ # Find calculator server
59
+ server_id = find_calculator_server(client)
60
+ if not server_id:
61
+ return
62
+
63
+ print(f"Server: {server_id}")
64
+ print()
65
+
66
+ # Filter for calculator server only
67
+ server_filter = "securemcp-calculator"
68
+
69
+ # 1. Discover tools
70
+ print(f"list_tools('{server_filter}'):")
71
+ print("-" * 40)
72
+ tools = await client.list_tools(server_name=server_filter)
73
+ for t in tools:
74
+ print(f" - {t['name']}")
75
+ print()
76
+
77
+ # 2. Discover resources
78
+ print(f"list_resources('{server_filter}'):")
79
+ print("-" * 40)
80
+ resources = await client.list_resources(server_name=server_filter)
81
+ for r in resources:
82
+ print(f" - {r['uri']}")
83
+ print()
84
+
85
+ # 3. Discover prompts
86
+ print(f"list_prompts('{server_filter}'):")
87
+ print("-" * 40)
88
+ prompts = await client.list_prompts(server_name=server_filter)
89
+ for p in prompts:
90
+ print(f" - {p['name']}")
91
+ print()
92
+
93
+ # Set default server for convenience
94
+ client.set_default_server(server_id)
95
+
96
+ # 4. Invoke a tool to create some history
97
+ print("Creating history with calculate():")
98
+ print("-" * 40)
99
+ for op, a, b in [("add", 5, 3), ("multiply", 4, 7)]:
100
+ result = client.macaw_client.invoke_tool(
101
+ "calculate",
102
+ {"operation": op, "a": a, "b": b},
103
+ target_agent=server_id
104
+ )
105
+ print(f" calculate({op}, {a}, {b}) = {result['result']}")
106
+ print()
107
+
108
+ # 5. Read a resource
109
+ print("get_resource('calc://history'):")
110
+ print("-" * 40)
111
+ history = await client.get_resource("calc://history")
112
+ print(f" History count: {history.get('count', 0)}")
113
+ for entry in history.get("history", [])[-3:]: # Last 3 entries
114
+ print(f" - {entry}")
115
+ print()
116
+
117
+ # 6. Get a prompt
118
+ print("get_prompt('calculation_prompt', {numbers: '1,2,3'}):")
119
+ print("-" * 40)
120
+ prompt = await client.get_prompt("calculation_prompt", {"numbers": "1,2,3"})
121
+ print(f" {prompt}")
122
+ print()
123
+
124
+ print("=" * 50)
125
+ print("Done!")
126
+ print("=" * 50)
127
+
128
+
129
+ if __name__ == "__main__":
130
+ try:
131
+ asyncio.run(main())
132
+ except Exception as e:
133
+ err = str(e)
134
+ print("\n" + "=" * 50)
135
+ if "Connection refused" in err or "connect" in err.lower():
136
+ print("ERROR: Cannot connect to MACAW")
137
+ print("Fix: Ensure MACAW is running")
138
+ else:
139
+ print(f"ERROR: {e}")
140
+ print("=" * 50)
141
+ import sys
142
+ sys.exit(1)
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 1c_logging_client.py - Context Logging and Audit
4
+
5
+ Tests logging features in the calculator server:
6
+ - ctx.info(), ctx.debug(), ctx.warning() during calculate()
7
+ - ctx.audit() creates signed audit entries
8
+
9
+ Prerequisites:
10
+ - MACAW SDK installed (pip install macaw-client macaw-adapters)
11
+ - Calculator server running (securemcp_calculator.py)
12
+
13
+ Run:
14
+ # Terminal 1: Start the server
15
+ python securemcp_calculator.py
16
+
17
+ # Terminal 2: Run this client
18
+ python 1c_logging_client.py
19
+
20
+ Check MACAW logs to see the logged events:
21
+ ~/.macaw/data/tenants/<tenant>/logs/events.log
22
+ """
23
+
24
+ from macaw_adapters.mcp import Client
25
+
26
+
27
+ def find_calculator_server(client):
28
+ """Find the most recent calculator server."""
29
+ agents = client.macaw_client.list_agents(agent_type="app")
30
+ # Match exactly "securemcp-calculator:" to avoid stale "securemcp-securemcp-calculator"
31
+ calc_servers = [
32
+ a["agent_id"] for a in agents
33
+ if "/app:securemcp-calculator:" in a.get("agent_id", "")
34
+ and "/tool." not in a.get("agent_id", "")
35
+ ]
36
+ return calc_servers[0] if calc_servers else None
37
+
38
+
39
+ def main():
40
+ print("=" * 50)
41
+ print("Example 1c: Logging & Audit Demo")
42
+ print("=" * 50)
43
+
44
+ client = Client("logging-test-client")
45
+ print(f"Client: {client.client_id}")
46
+
47
+ server = find_calculator_server(client)
48
+ if not server:
49
+ print("\nNo calculator server found!")
50
+ print("Start it first: python3 securemcp_calculator.py")
51
+ return
52
+
53
+ print(f"Server: {server}")
54
+ print()
55
+
56
+ # Test calculate() - generates ctx.info(), ctx.debug(), ctx.audit()
57
+ print("Testing calculate() - generates logging and audit:")
58
+ print("-" * 40)
59
+
60
+ tests = [
61
+ ("add", 10, 5),
62
+ ("multiply", 7, 8),
63
+ ("subtract", 20, 7),
64
+ ("divide", 100, 4),
65
+ ]
66
+
67
+ for op, a, b in tests:
68
+ result = client.macaw_client.invoke_tool(
69
+ "calculate",
70
+ {"operation": op, "a": a, "b": b},
71
+ target_agent=server
72
+ )
73
+ print(f" {op}({a}, {b}) = {result}")
74
+
75
+ print()
76
+ print("=" * 50)
77
+ print("Check MACAW logs at:")
78
+ print(" ~/.macaw/data/tenants/<tenant>/logs/events.log")
79
+ print()
80
+ print("Look for:")
81
+ print(" - tool_log: level=info, message='Calculating: ...'")
82
+ print(" - tool_log: level=debug, message='Result computed: ...'")
83
+ print(" - audit: action=calculation (signed)")
84
+ print("=" * 50)
85
+
86
+
87
+ if __name__ == "__main__":
88
+ try:
89
+ main()
90
+ except Exception as e:
91
+ err = str(e)
92
+ print("\n" + "=" * 50)
93
+ if "Connection refused" in err or "connect" in err.lower():
94
+ print("ERROR: Cannot connect to MACAW")
95
+ print("Fix: Ensure MACAW is running")
96
+ else:
97
+ print(f"ERROR: {e}")
98
+ print("=" * 50)
99
+ import sys
100
+ sys.exit(1)
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 1d_progress_client.py - Progress Reporting
4
+
5
+ Tests progress reporting in the calculator server:
6
+ - batch_calculate() reports progress via ctx.report_progress()
7
+ - Progress events are emitted to MACAW audit system
8
+
9
+ Prerequisites:
10
+ - MACAW SDK installed (pip install macaw-client macaw-adapters)
11
+ - Calculator server running (securemcp_calculator.py)
12
+
13
+ Run:
14
+ # Terminal 1: Start the server
15
+ python securemcp_calculator.py
16
+
17
+ # Terminal 2: Run this client
18
+ python 1d_progress_client.py
19
+ """
20
+
21
+ from macaw_adapters.mcp import Client
22
+
23
+
24
+ def find_calculator_server(client):
25
+ """Find the most recent calculator server."""
26
+ agents = client.macaw_client.list_agents(agent_type="app")
27
+ # Match exactly "/app:securemcp-calculator:" to avoid stale registrations
28
+ calc_servers = [
29
+ a["agent_id"] for a in agents
30
+ if "/app:securemcp-calculator:" in a.get("agent_id", "")
31
+ and "/tool." not in a.get("agent_id", "")
32
+ ]
33
+ return calc_servers[0] if calc_servers else None
34
+
35
+
36
+ def main():
37
+ print("=" * 50)
38
+ print("Example 1d: Progress Reporting Demo")
39
+ print("=" * 50)
40
+
41
+ client = Client("progress-test-client")
42
+ print(f"Client: {client.client_id}")
43
+
44
+ server = find_calculator_server(client)
45
+ if not server:
46
+ print("\nNo calculator server found!")
47
+ print("Start it first: python3 securemcp_calculator.py")
48
+ return
49
+
50
+ print(f"Server: {server}")
51
+ print()
52
+
53
+ # Test batch_calculate() - generates progress events
54
+ print("Testing batch_calculate() - generates progress events:")
55
+ print("-" * 40)
56
+
57
+ calculations = [
58
+ {"op": "add", "a": 1, "b": 2},
59
+ {"op": "multiply", "a": 3, "b": 4},
60
+ {"op": "subtract", "a": 10, "b": 3},
61
+ {"op": "divide", "a": 100, "b": 5},
62
+ {"op": "add", "a": 50, "b": 50},
63
+ ]
64
+
65
+ print(f" Sending batch of {len(calculations)} calculations...")
66
+ result = client.macaw_client.invoke_tool(
67
+ "batch_calculate",
68
+ {"calculations": calculations},
69
+ target_agent=server
70
+ )
71
+
72
+ print(f" Batch result: {result['count']} calculations")
73
+ for r in result.get("results", []):
74
+ print(f" {r['expression']} = {r.get('result', r.get('error'))}")
75
+
76
+ print()
77
+ print("=" * 50)
78
+ print("Check MACAW logs for progress events:")
79
+ print(" - Progress 0%: 'Processing 5 calculations'")
80
+ print(" - Progress 20%: 'Calculating 1/5: ...'")
81
+ print(" - Progress 40%: 'Calculating 2/5: ...'")
82
+ print(" - ...")
83
+ print(" - Progress 100%: 'Batch complete'")
84
+ print("=" * 50)
85
+
86
+
87
+ if __name__ == "__main__":
88
+ try:
89
+ main()
90
+ except Exception as e:
91
+ err = str(e)
92
+ print("\n" + "=" * 50)
93
+ if "Connection refused" in err or "connect" in err.lower():
94
+ print("ERROR: Cannot connect to MACAW")
95
+ print("Fix: Ensure MACAW is running")
96
+ else:
97
+ print(f"ERROR: {e}")
98
+ print("=" * 50)
99
+ import sys
100
+ sys.exit(1)
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 1e_sampling_client.py - MCP Sampling (Server->Client LLM)
4
+
5
+ Demonstrates client.set_sampling_handler() for server->client LLM requests.
6
+
7
+ The client registers an LLM handler that the server's ctx.sample() calls.
8
+ Set ANTHROPIC_API_KEY environment variable to use real Claude API,
9
+ otherwise falls back to mock responses.
10
+
11
+ Prerequisites:
12
+ - MACAW SDK installed (pip install macaw-client macaw-adapters)
13
+ - Sampling server running (1e_sampling_server.py)
14
+ - ANTHROPIC_API_KEY environment variable (optional, for real Claude)
15
+
16
+ Run:
17
+ # Terminal 1: Start the server
18
+ python 1e_sampling_server.py
19
+
20
+ # Terminal 2: Run this client (with optional API key)
21
+ export ANTHROPIC_API_KEY=sk-ant-... # Optional
22
+ python 1e_sampling_client.py
23
+ """
24
+
25
+ import os
26
+
27
+ from macaw_adapters.mcp import Client
28
+
29
+ MODEL = "claude-opus-4-8"
30
+
31
+
32
+ def find_sampling_server(client):
33
+ """Find the most recent sampling demo server."""
34
+ agents = client.macaw_client.list_agents(agent_type="app")
35
+ matching = [
36
+ a["agent_id"] for a in agents
37
+ if "sampling-demo" in a.get("agent_id", "")
38
+ and "/tool." not in a.get("agent_id", "")
39
+ ]
40
+ return matching[0] if matching else None # Return first (most recent)
41
+
42
+
43
+ def create_llm_handler():
44
+ """Create LLM handler - real Claude if API key available, else mock."""
45
+ api_key = os.environ.get("ANTHROPIC_API_KEY")
46
+
47
+ if api_key:
48
+ try:
49
+ import anthropic
50
+ client = anthropic.Anthropic(api_key=api_key)
51
+ print(f"Using Claude API ({MODEL})")
52
+
53
+ def claude_handler(prompt, system_prompt=None, max_tokens=1000, temperature=None, **kwargs):
54
+ print(f" [Claude] Prompt: {prompt[:60]}...")
55
+ # temperature is intentionally not forwarded: this handler owns the
56
+ # model choice, and current Claude models reject temperature/top_p/
57
+ # top_k with a 400. MCP treats sampling params as ignorable hints.
58
+ response = client.messages.create(
59
+ model=MODEL,
60
+ max_tokens=max_tokens,
61
+ system=system_prompt or "You are a helpful assistant.",
62
+ messages=[{"role": "user", "content": prompt}]
63
+ )
64
+ result = response.content[0].text
65
+ print(f" [Claude] Response: {result[:60]}...")
66
+ return result
67
+
68
+ return claude_handler
69
+ except ImportError:
70
+ print("anthropic package not installed, falling back to mock")
71
+ except Exception as e:
72
+ print(f"Error initializing Claude: {e}, falling back to mock")
73
+
74
+ # Mock handler fallback
75
+ print("Using mock LLM (set ANTHROPIC_API_KEY for real Claude)")
76
+
77
+ def mock_handler(prompt, system_prompt=None, max_tokens=1000, temperature=None, **kwargs):
78
+ print(f" [Mock] Prompt: {prompt[:60]}...")
79
+ if "summarize" in prompt.lower():
80
+ return "This is a mock summary of the provided text."
81
+ elif "sentiment" in prompt.lower():
82
+ return '{"sentiment": "positive", "confidence": 0.85}'
83
+ return "Mock LLM response"
84
+
85
+ return mock_handler
86
+
87
+
88
+ def main():
89
+ print("=" * 50)
90
+ print("Example 1e: Sampling Demo Client")
91
+ print("=" * 50)
92
+
93
+ client = Client("sampling-client")
94
+ print(f"Client: {client.client_id}")
95
+
96
+ # Create and register LLM handler (Claude or mock)
97
+ llm_handler = create_llm_handler()
98
+ client.set_sampling_handler(llm_handler)
99
+ print()
100
+
101
+ server = find_sampling_server(client)
102
+ if not server:
103
+ print("\nNo sampling-demo server found!")
104
+ print("Start it first: python3 1e_sampling_server.py")
105
+ return
106
+
107
+ print(f"Server: {server}")
108
+ print()
109
+
110
+ # Test summarize
111
+ print("Testing summarize():")
112
+ print("-" * 40)
113
+ result = client.macaw_client.invoke_tool(
114
+ "summarize",
115
+ {"text": "The quick brown fox jumps over the lazy dog. This sentence contains every letter of the alphabet and is commonly used for typing practice.", "max_length": 50},
116
+ target_agent=server
117
+ )
118
+ print(f" Result: {result}")
119
+ print()
120
+
121
+ # Test analyze_sentiment
122
+ print("Testing analyze_sentiment():")
123
+ print("-" * 40)
124
+ result = client.macaw_client.invoke_tool(
125
+ "analyze_sentiment",
126
+ {"text": "I love this product! It's amazing and works perfectly!"},
127
+ target_agent=server
128
+ )
129
+ print(f" Result: {result}")
130
+ print()
131
+
132
+ print("=" * 50)
133
+ print("Done!")
134
+ print("=" * 50)
135
+
136
+
137
+ if __name__ == "__main__":
138
+ try:
139
+ main()
140
+ except Exception as e:
141
+ err = str(e)
142
+ print("\n" + "=" * 50)
143
+ if "Connection refused" in err or "connect" in err.lower():
144
+ print("ERROR: Cannot connect to MACAW")
145
+ print("Fix: Ensure MACAW is running")
146
+ else:
147
+ print(f"ERROR: {e}")
148
+ print("=" * 50)
149
+ import sys
150
+ sys.exit(1)
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Example 1e: MCP Sampling - SERVER
4
+
5
+ Demonstrates ctx.sample() - server requesting LLM completions from client.
6
+
7
+ The server tools here use ctx.sample() to request AI assistance from
8
+ the client's configured LLM handler.
9
+
10
+ Run this server, then run 1e_sampling_client.py in another terminal.
11
+ """
12
+
13
+ from macaw_adapters.mcp import SecureMCP, Context
14
+
15
+ mcp = SecureMCP("sampling-demo")
16
+
17
+
18
+ @mcp.tool(description="Summarize text using client's LLM")
19
+ async def summarize(ctx: Context, text: str, max_length: int = 100) -> dict:
20
+ """Summarize text via client's LLM (ctx.sample)."""
21
+ ctx.info(f"Summarizing {len(text)} characters")
22
+
23
+ prompt = f"Summarize in {max_length} words or less:\n\n{text}\n\nSummary:"
24
+
25
+ try:
26
+ summary = await ctx.sample(
27
+ prompt=prompt,
28
+ system_prompt="You are a concise summarization assistant.",
29
+ max_tokens=200,
30
+ temperature=0.3
31
+ )
32
+ return {"summary": summary, "original_length": len(text)}
33
+ except Exception as e:
34
+ ctx.error(f"Sampling failed: {e}")
35
+ return {"error": str(e)}
36
+
37
+
38
+ @mcp.tool(description="Analyze sentiment using client's LLM")
39
+ async def analyze_sentiment(ctx: Context, text: str) -> dict:
40
+ """Analyze sentiment via client's LLM."""
41
+ ctx.info("Analyzing sentiment")
42
+
43
+ prompt = f"""Analyze sentiment. Respond with JSON:
44
+ {{"sentiment": "positive/negative/neutral", "confidence": 0.0-1.0}}
45
+
46
+ Text: {text}
47
+
48
+ JSON:"""
49
+
50
+ try:
51
+ analysis = await ctx.sample(
52
+ prompt=prompt,
53
+ system_prompt="You are a sentiment analyzer. Respond only with JSON.",
54
+ max_tokens=100,
55
+ temperature=0.1
56
+ )
57
+ return {"text": text[:50] + "..." if len(text) > 50 else text, "analysis": analysis}
58
+ except Exception as e:
59
+ return {"error": str(e)}
60
+
61
+
62
+ if __name__ == "__main__":
63
+ import sys
64
+
65
+ # Optional transport for native (non-MACAW) MCP clients:
66
+ # python 1e_sampling_server.py MACAW mesh only (use 1e_sampling_client.py)
67
+ # python 1e_sampling_server.py stdio also serve native MCP clients
68
+ transport = sys.argv[1] if len(sys.argv) > 1 else None
69
+
70
+ # stdout is the JSON-RPC stream when transport="stdio", so the banner goes to stderr.
71
+ out = sys.stderr
72
+
73
+ print("=" * 50, file=out)
74
+ print("Example 1e: Sampling Demo Server", file=out)
75
+ print("=" * 50, file=out)
76
+ print(file=out)
77
+ print("Tools: summarize, analyze_sentiment", file=out)
78
+ print(file=out)
79
+ print("These tools use ctx.sample() to request LLM completions", file=out)
80
+ print("from the client's configured handler.", file=out)
81
+ print(file=out)
82
+ if transport:
83
+ print(f"Also serving native MCP clients over: {transport}", file=out)
84
+ print("Run client: python3 3c_native_mcp_sampling.py", file=out)
85
+ else:
86
+ print("Run client: python3 1e_sampling_client.py", file=out)
87
+ print(file=out)
88
+ print("Press Ctrl+C to stop", file=out)
89
+ print("=" * 50, file=out)
90
+ mcp.run(transport=transport)