@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.
- package/LICENSE.md +339 -0
- package/bin/install.js +422 -0
- package/package.json +37 -4
- package/payload/README.md +56 -0
- package/payload/secureAI/LICENSE +190 -0
- package/payload/secureAI/NOTICE +5 -0
- package/payload/secureAI/demos/policy-controls/1_uncontrolled.py +139 -0
- package/payload/secureAI/demos/policy-controls/2_with_macaw.py +139 -0
- package/payload/secureAI/demos/policy-controls/3_with_user_policies.py +162 -0
- package/payload/secureAI/demos/policy-controls/credentials.txt +12 -0
- package/payload/secureAI/demos/policy-controls/malicious_report.txt +21 -0
- package/payload/secureAI/demos/policy-controls/sales_report.txt +20 -0
- package/payload/secureAI/demos/tutorial-1-rbac/README.md +143 -0
- package/payload/secureAI/demos/tutorial-1-rbac/app.py +147 -0
- package/payload/secureAI/demos/tutorial-1-rbac/authprompts_demo.py +306 -0
- package/payload/secureAI/demos/tutorial-1-rbac/config/claims-config.yaml +152 -0
- package/payload/secureAI/demos/tutorial-1-rbac/demo_secureopenai_with_policies.py +150 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/bu_Analytics.json +8 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/company_FinTech_Corp.json +70 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/llm-base.json +69 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/team_Reporting.json +8 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/user_alice.json +32 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/user_bob.json +28 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/user_carol.json +34 -0
- package/payload/secureAI/demos/tutorial-1-rbac/requirements.txt +6 -0
- package/payload/secureAI/demos/tutorial-1-rbac/setup/auth0_complete_setup.sh +392 -0
- package/payload/secureAI/demos/tutorial-1-rbac/setup/keycloak_complete_setup.sh +320 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/alation-remote-proxy.json +155 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/bu.json +22 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/company.json +17 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/user_alice.json +48 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/user_bob.json +83 -0
- package/payload/secureAI/demos/tutorial-2-intent/README.md +213 -0
- package/payload/secureAI/demos/tutorial-2-intent/script.py +188 -0
- package/payload/secureAI/demos/tutorial-2-intent/setup/alation_verifier.py +177 -0
- package/payload/secureAI/demos/tutorial-2-intent/setup/get_alation_user_token.py +180 -0
- package/payload/secureAI/examples/README.md +244 -0
- package/payload/secureAI/examples/anthropic/anthropic_1a_dropin_simple.py +103 -0
- package/payload/secureAI/examples/anthropic/anthropic_1b_multiuser_bind.py +190 -0
- package/payload/secureAI/examples/anthropic/anthropic_1b_multiuser_bind_streaming.py +190 -0
- package/payload/secureAI/examples/anthropic/anthropic_1c_a2a_invoke.py +156 -0
- package/payload/secureAI/examples/attestations/1a_trade_alice.py +215 -0
- package/payload/secureAI/examples/attestations/1a_trade_bob.py +164 -0
- package/payload/secureAI/examples/attestations/1b_grant_admin.py +170 -0
- package/payload/secureAI/examples/attestations/1b_grant_researcher.py +231 -0
- package/payload/secureAI/examples/attestations/README.md +128 -0
- package/payload/secureAI/examples/langchain/langchain_1a_dropin_simple.py +199 -0
- package/payload/secureAI/examples/langchain/langchain_1b_multiuser.py +262 -0
- package/payload/secureAI/examples/langchain/langchain_1c_orchestration.py +332 -0
- package/payload/secureAI/examples/langchain/langchain_1d_llm_openai.py +130 -0
- package/payload/secureAI/examples/langchain/langchain_1e_llm_anthropic.py +130 -0
- package/payload/secureAI/examples/langchain/langchain_1f_memory.py +214 -0
- package/payload/secureAI/examples/litellm/litellm_1a_dropin_simple.py +131 -0
- package/payload/secureAI/examples/mcp/1a_simple_invocation.py +115 -0
- package/payload/secureAI/examples/mcp/1b_discovery_and_resources.py +142 -0
- package/payload/secureAI/examples/mcp/1c_logging_client.py +100 -0
- package/payload/secureAI/examples/mcp/1d_progress_client.py +100 -0
- package/payload/secureAI/examples/mcp/1e_sampling_client.py +150 -0
- package/payload/secureAI/examples/mcp/1e_sampling_server.py +90 -0
- package/payload/secureAI/examples/mcp/1f_elicitation_client.py +127 -0
- package/payload/secureAI/examples/mcp/1f_elicitation_server.py +73 -0
- package/payload/secureAI/examples/mcp/1g_roots_client.py +122 -0
- package/payload/secureAI/examples/mcp/1g_roots_server.py +85 -0
- package/payload/secureAI/examples/mcp/2a_proxy_snowflake.py +212 -0
- package/payload/secureAI/examples/mcp/2b_proxy_databricks.py +299 -0
- package/payload/secureAI/examples/mcp/2c_proxy_stdio_fetch.py +91 -0
- package/payload/secureAI/examples/mcp/2d_proxy_stdio_salesforce.py +119 -0
- package/payload/secureAI/examples/mcp/2e_proxy_multiuser.py +136 -0
- package/payload/secureAI/examples/mcp/2f_proxy_stdio_databricks.py +144 -0
- package/payload/secureAI/examples/mcp/3a_native_mcp_client.py +104 -0
- package/payload/secureAI/examples/mcp/3b_native_mcp_proxy.py +114 -0
- package/payload/secureAI/examples/mcp/3c_native_mcp_sampling.py +180 -0
- package/payload/secureAI/examples/mcp/3d_native_mcp_2026.py +135 -0
- package/payload/secureAI/examples/mcp/securemcp_calculator.py +248 -0
- package/payload/secureAI/examples/mcp/securemcp_server.py +75 -0
- package/payload/secureAI/examples/openai/openai_1a_dropin_simple.py +102 -0
- package/payload/secureAI/examples/openai/openai_1b_multiuser_bind.py +193 -0
- package/payload/secureAI/examples/openai/openai_1b_multiuser_bind_streaming.py +199 -0
- package/payload/secureAI/examples/openai/openai_1c_a2a_invoke.py +156 -0
- package/payload/secureAI/examples/openai/openai_1d_invocation_patterns.py +337 -0
- package/payload/secureAI/examples/phishing-resistance/1a_login_alice.py +180 -0
- package/payload/secureAI/examples/phishing-resistance/1b_trade_alice.py +241 -0
- package/payload/secureAI/examples/phishing-resistance/1b_trade_bob.py +164 -0
- package/payload/secureAI/examples/phishing-resistance/README.md +124 -0
- package/payload/secureAI/examples/phishing-resistance/get_token.py +114 -0
- package/payload/secureAI/examples/pydantic_ai/README.md +88 -0
- package/payload/secureAI/examples/pydantic_ai/policies/app_pydantic-agent.json +10 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1a_dropin_simple.py +90 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1b_model_routing.py +85 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1c_native_tools.py +83 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1d_multiuser_bind.py +104 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1e_mcp_compose.py +92 -0
- package/payload/secureAI/macaw_adapters/__init__.py +55 -0
- package/payload/secureAI/macaw_adapters/anthropic/REFERENCE.md +374 -0
- package/payload/secureAI/macaw_adapters/anthropic/__init__.py +22 -0
- package/payload/secureAI/macaw_adapters/anthropic/secure_anthropic.py +792 -0
- package/payload/secureAI/macaw_adapters/anthropic_ext_tools/__init__.py +25 -0
- package/payload/secureAI/macaw_adapters/anthropic_ext_tools/secure_anthropic.py +840 -0
- package/payload/secureAI/macaw_adapters/langchain/REFERENCE.md +234 -0
- package/payload/secureAI/macaw_adapters/langchain/__init__.py +100 -0
- package/payload/secureAI/macaw_adapters/langchain/_utils.py +83 -0
- package/payload/secureAI/macaw_adapters/langchain/agents.py +296 -0
- package/payload/secureAI/macaw_adapters/langchain/anthropic.py +456 -0
- package/payload/secureAI/macaw_adapters/langchain/callbacks.py +306 -0
- package/payload/secureAI/macaw_adapters/langchain/memory.py +352 -0
- package/payload/secureAI/macaw_adapters/langchain/openai.py +410 -0
- package/payload/secureAI/macaw_adapters/langchain/tools.py +174 -0
- package/payload/secureAI/macaw_adapters/litellm/README.md +216 -0
- package/payload/secureAI/macaw_adapters/litellm/__init__.py +143 -0
- package/payload/secureAI/macaw_adapters/litellm/secure_litellm.py +686 -0
- package/payload/secureAI/macaw_adapters/mcp/REFERENCE.md +460 -0
- package/payload/secureAI/macaw_adapters/mcp/__init__.py +65 -0
- package/payload/secureAI/macaw_adapters/mcp/_endpoint.py +193 -0
- package/payload/secureAI/macaw_adapters/mcp/_endpoint2.py +225 -0
- package/payload/secureAI/macaw_adapters/mcp/_serve.py +25 -0
- package/payload/secureAI/macaw_adapters/mcp/client.py +721 -0
- package/payload/secureAI/macaw_adapters/mcp/decorators.py +228 -0
- package/payload/secureAI/macaw_adapters/mcp/fastmcp_compat.py +353 -0
- package/payload/secureAI/macaw_adapters/mcp/mcp.py +785 -0
- package/payload/secureAI/macaw_adapters/mcp/proxy.py +626 -0
- package/payload/secureAI/macaw_adapters/mcp/server.py +317 -0
- package/payload/secureAI/macaw_adapters/mcp/subscriptions.py +450 -0
- package/payload/secureAI/macaw_adapters/openai/REFERENCE.md +368 -0
- package/payload/secureAI/macaw_adapters/openai/__init__.py +22 -0
- package/payload/secureAI/macaw_adapters/openai/secure_openai.py +833 -0
- package/payload/secureAI/macaw_adapters/openai_ext_tools/REFERENCE.md +368 -0
- package/payload/secureAI/macaw_adapters/openai_ext_tools/__init__.py +25 -0
- package/payload/secureAI/macaw_adapters/openai_ext_tools/secure_openai.py +876 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/__init__.py +36 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/_core.py +257 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/agent.py +218 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/model.py +99 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/toolset.py +86 -0
- package/payload/secureAI/test_harness_quick.py +296 -0
- package/wheels/macaw_adapters-0.9.10.1-py3-none-any.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp312-cp312-macosx_11_0_arm64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp312-cp312-manylinux_2_17_x86_64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp314-cp314-macosx_11_0_arm64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp314-cp314-manylinux_2_17_x86_64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp314-cp314-win_amd64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp39-cp39-macosx_11_0_arm64.whl +0 -0
- 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)
|