@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,127 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
1f_elicitation_client.py - MCP Elicitation (Server->User Input)
|
|
4
|
+
|
|
5
|
+
Demonstrates client.set_elicitation_handler() for server->user input.
|
|
6
|
+
|
|
7
|
+
The client registers an input handler that prompts the user when
|
|
8
|
+
the server's ctx.elicit() is called.
|
|
9
|
+
|
|
10
|
+
NOTE: This example requires interactive user input and is not suitable
|
|
11
|
+
for automated test harnesses.
|
|
12
|
+
|
|
13
|
+
Prerequisites:
|
|
14
|
+
- MACAW SDK installed (pip install macaw-client macaw-adapters)
|
|
15
|
+
- Elicitation server running (1f_elicitation_server.py)
|
|
16
|
+
|
|
17
|
+
Run:
|
|
18
|
+
# Terminal 1: Start the server
|
|
19
|
+
python 1f_elicitation_server.py
|
|
20
|
+
|
|
21
|
+
# Terminal 2: Run this client (interactive)
|
|
22
|
+
python 1f_elicitation_client.py
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from macaw_adapters.mcp import Client
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def find_elicitation_server(client):
|
|
29
|
+
"""Find the most recent elicitation demo server."""
|
|
30
|
+
agents = client.macaw_client.list_agents(agent_type="app")
|
|
31
|
+
matching = [
|
|
32
|
+
a["agent_id"] for a in agents
|
|
33
|
+
if "elicitation-demo" in a.get("agent_id", "")
|
|
34
|
+
and "/tool." not in a.get("agent_id", "")
|
|
35
|
+
]
|
|
36
|
+
return matching[0] if matching else None # Return first (most recent)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def main():
|
|
40
|
+
print("=" * 50)
|
|
41
|
+
print("Example 1f: Elicitation Demo Client")
|
|
42
|
+
print("=" * 50)
|
|
43
|
+
|
|
44
|
+
client = Client("elicitation-client")
|
|
45
|
+
print(f"Client: {client.client_id}")
|
|
46
|
+
|
|
47
|
+
# Register interactive input handler
|
|
48
|
+
def input_handler(prompt, options, input_type, default, required, **kwargs):
|
|
49
|
+
print()
|
|
50
|
+
if input_type == "confirm":
|
|
51
|
+
response = input(f" {prompt} (y/n) [{default or 'n'}]: ").strip().lower()
|
|
52
|
+
if not response:
|
|
53
|
+
response = (default or "n").lower()
|
|
54
|
+
return response in ("y", "yes")
|
|
55
|
+
|
|
56
|
+
elif input_type == "select" and options:
|
|
57
|
+
print(f" {prompt}")
|
|
58
|
+
for i, opt in enumerate(options):
|
|
59
|
+
print(f" {i+1}. {opt}")
|
|
60
|
+
while True:
|
|
61
|
+
try:
|
|
62
|
+
choice = input(f" Enter number (1-{len(options)}): ").strip()
|
|
63
|
+
idx = int(choice) - 1
|
|
64
|
+
if 0 <= idx < len(options):
|
|
65
|
+
return options[idx]
|
|
66
|
+
except ValueError:
|
|
67
|
+
pass
|
|
68
|
+
print(" Invalid choice, try again")
|
|
69
|
+
|
|
70
|
+
else: # text
|
|
71
|
+
response = input(f" {prompt}: ").strip()
|
|
72
|
+
return response or default
|
|
73
|
+
|
|
74
|
+
client.set_elicitation_handler(input_handler)
|
|
75
|
+
print("Elicitation handler registered (interactive)")
|
|
76
|
+
print()
|
|
77
|
+
|
|
78
|
+
server = find_elicitation_server(client)
|
|
79
|
+
if not server:
|
|
80
|
+
print("\nNo elicitation-demo server found!")
|
|
81
|
+
print("Start it first: python3 1f_elicitation_server.py")
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
print(f"Server: {server}")
|
|
85
|
+
print()
|
|
86
|
+
|
|
87
|
+
# Test create_profile
|
|
88
|
+
print("Testing create_profile():")
|
|
89
|
+
print("-" * 40)
|
|
90
|
+
result = client.macaw_client.invoke_tool(
|
|
91
|
+
"create_profile",
|
|
92
|
+
{},
|
|
93
|
+
target_agent=server
|
|
94
|
+
)
|
|
95
|
+
print(f"\n Result: {result}")
|
|
96
|
+
print()
|
|
97
|
+
|
|
98
|
+
# Test delete_item
|
|
99
|
+
print("Testing delete_item():")
|
|
100
|
+
print("-" * 40)
|
|
101
|
+
result = client.macaw_client.invoke_tool(
|
|
102
|
+
"delete_item",
|
|
103
|
+
{"item_name": "important_file.txt"},
|
|
104
|
+
target_agent=server
|
|
105
|
+
)
|
|
106
|
+
print(f"\n Result: {result}")
|
|
107
|
+
|
|
108
|
+
print()
|
|
109
|
+
print("=" * 50)
|
|
110
|
+
print("Elicitation enables human-in-the-loop workflows")
|
|
111
|
+
print("=" * 50)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
if __name__ == "__main__":
|
|
115
|
+
try:
|
|
116
|
+
main()
|
|
117
|
+
except Exception as e:
|
|
118
|
+
err = str(e)
|
|
119
|
+
print("\n" + "=" * 50)
|
|
120
|
+
if "Connection refused" in err or "connect" in err.lower():
|
|
121
|
+
print("ERROR: Cannot connect to MACAW")
|
|
122
|
+
print("Fix: Ensure MACAW is running")
|
|
123
|
+
else:
|
|
124
|
+
print(f"ERROR: {e}")
|
|
125
|
+
print("=" * 50)
|
|
126
|
+
import sys
|
|
127
|
+
sys.exit(1)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Example 1f: MCP Elicitation - SERVER
|
|
4
|
+
|
|
5
|
+
Demonstrates ctx.elicit() - server requesting user input from client.
|
|
6
|
+
|
|
7
|
+
The server tools here use ctx.elicit() to interactively request
|
|
8
|
+
information from the user during execution.
|
|
9
|
+
|
|
10
|
+
Run this server, then run 1f_elicitation_client.py in another terminal.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from macaw_adapters.mcp import SecureMCP, Context
|
|
14
|
+
|
|
15
|
+
mcp = SecureMCP("elicitation-demo")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@mcp.tool(description="Create user profile interactively")
|
|
19
|
+
async def create_profile(ctx: Context) -> dict:
|
|
20
|
+
"""Create a profile by asking the user questions."""
|
|
21
|
+
ctx.info("Starting profile creation")
|
|
22
|
+
|
|
23
|
+
name = await ctx.elicit(
|
|
24
|
+
prompt="What is your name?",
|
|
25
|
+
input_type="text",
|
|
26
|
+
required=True
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
role = await ctx.elicit(
|
|
30
|
+
prompt="What is your role?",
|
|
31
|
+
options=["Developer", "Designer", "Manager", "Other"],
|
|
32
|
+
input_type="select"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
ctx.audit(action="profile_created", target="user_profile", outcome="success", name=name)
|
|
36
|
+
|
|
37
|
+
return {"name": name, "role": role, "status": "created"}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@mcp.tool(description="Delete with confirmation")
|
|
41
|
+
async def delete_item(ctx: Context, item_name: str) -> dict:
|
|
42
|
+
"""Delete an item with user confirmation."""
|
|
43
|
+
ctx.info(f"Request to delete: {item_name}")
|
|
44
|
+
|
|
45
|
+
confirmed = await ctx.elicit(
|
|
46
|
+
prompt=f"Delete '{item_name}'? This cannot be undone.",
|
|
47
|
+
input_type="confirm",
|
|
48
|
+
default="no"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if confirmed:
|
|
52
|
+
ctx.audit(action="delete", target=item_name, outcome="success")
|
|
53
|
+
return {"deleted": item_name, "status": "deleted"}
|
|
54
|
+
else:
|
|
55
|
+
ctx.info("Deletion cancelled")
|
|
56
|
+
return {"deleted": None, "status": "cancelled"}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
print("=" * 50)
|
|
61
|
+
print("Example 1f: Elicitation Demo Server")
|
|
62
|
+
print("=" * 50)
|
|
63
|
+
print()
|
|
64
|
+
print("Tools: create_profile, delete_item")
|
|
65
|
+
print()
|
|
66
|
+
print("These tools use ctx.elicit() to request user input")
|
|
67
|
+
print("during execution (human-in-the-loop).")
|
|
68
|
+
print()
|
|
69
|
+
print("Run client: python3 1f_elicitation_client.py")
|
|
70
|
+
print()
|
|
71
|
+
print("Press Ctrl+C to stop")
|
|
72
|
+
print("=" * 50)
|
|
73
|
+
mcp.run()
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
1g_roots_client.py - MCP Roots via MAPL (Filesystem Boundaries)
|
|
4
|
+
|
|
5
|
+
Tests root boundaries declared by the server.
|
|
6
|
+
|
|
7
|
+
Demonstrates:
|
|
8
|
+
- list_roots() to discover server's allowed paths
|
|
9
|
+
- Access allowed within roots
|
|
10
|
+
- Access denied outside roots
|
|
11
|
+
|
|
12
|
+
Prerequisites:
|
|
13
|
+
- MACAW SDK installed (pip install macaw-client macaw-adapters)
|
|
14
|
+
- Roots server running (1g_roots_server.py)
|
|
15
|
+
|
|
16
|
+
Run:
|
|
17
|
+
# Terminal 1: Start the server
|
|
18
|
+
python 1g_roots_server.py
|
|
19
|
+
|
|
20
|
+
# Terminal 2: Run this client
|
|
21
|
+
python 1g_roots_client.py
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import asyncio
|
|
25
|
+
|
|
26
|
+
from macaw_adapters.mcp import Client
|
|
27
|
+
|
|
28
|
+
DEMO_DIR = "/tmp/securemcp-roots-demo"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def find_roots_server(client):
|
|
32
|
+
"""Find the most recent roots demo server."""
|
|
33
|
+
agents = client.macaw_client.list_agents(agent_type="app")
|
|
34
|
+
matching = [
|
|
35
|
+
a["agent_id"] for a in agents
|
|
36
|
+
if "roots-demo" in a.get("agent_id", "")
|
|
37
|
+
and "/tool." not in a.get("agent_id", "")
|
|
38
|
+
]
|
|
39
|
+
return matching[0] if matching else None # Return first (most recent)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def main():
|
|
43
|
+
print("=" * 50)
|
|
44
|
+
print("Example 1g: Roots Demo Client")
|
|
45
|
+
print("=" * 50)
|
|
46
|
+
|
|
47
|
+
client = Client("roots-client")
|
|
48
|
+
print(f"Client: {client.client_id}")
|
|
49
|
+
|
|
50
|
+
server = find_roots_server(client)
|
|
51
|
+
if not server:
|
|
52
|
+
print("\nNo roots-demo server found!")
|
|
53
|
+
print("Start it first: python3 1g_roots_server.py")
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
print(f"Server: {server}")
|
|
57
|
+
print()
|
|
58
|
+
|
|
59
|
+
# Discover roots
|
|
60
|
+
print("Discovering server roots (list_roots()):")
|
|
61
|
+
print("-" * 40)
|
|
62
|
+
|
|
63
|
+
async def show_roots():
|
|
64
|
+
roots = await client.list_roots(server_name="roots-demo")
|
|
65
|
+
for r in roots:
|
|
66
|
+
print(f" - {r['path']}")
|
|
67
|
+
return roots
|
|
68
|
+
|
|
69
|
+
asyncio.run(show_roots())
|
|
70
|
+
print()
|
|
71
|
+
|
|
72
|
+
# Test ALLOWED access
|
|
73
|
+
print(f"Testing list_dir('{DEMO_DIR}') - ALLOWED:")
|
|
74
|
+
print("-" * 40)
|
|
75
|
+
result = client.macaw_client.invoke_tool("list_dir", {"path": DEMO_DIR}, target_agent=server)
|
|
76
|
+
print(f" Result: {result}")
|
|
77
|
+
print()
|
|
78
|
+
|
|
79
|
+
print(f"Testing read_file('{DEMO_DIR}/allowed.txt') - ALLOWED:")
|
|
80
|
+
print("-" * 40)
|
|
81
|
+
result = client.macaw_client.invoke_tool("read_file", {"path": f"{DEMO_DIR}/allowed.txt"}, target_agent=server)
|
|
82
|
+
print(f" Result: {result}")
|
|
83
|
+
print()
|
|
84
|
+
|
|
85
|
+
# Test DENIED access (should raise exception or return error)
|
|
86
|
+
print("Testing list_dir('/etc') - DENIED (outside roots):")
|
|
87
|
+
print("-" * 40)
|
|
88
|
+
try:
|
|
89
|
+
result = client.macaw_client.invoke_tool("list_dir", {"path": "/etc"}, target_agent=server)
|
|
90
|
+
print(f" Result: {result}")
|
|
91
|
+
except Exception as e:
|
|
92
|
+
print(f" ✓ Access denied (as expected): {e}")
|
|
93
|
+
print()
|
|
94
|
+
|
|
95
|
+
print("Testing read_file('/etc/passwd') - DENIED:")
|
|
96
|
+
print("-" * 40)
|
|
97
|
+
try:
|
|
98
|
+
result = client.macaw_client.invoke_tool("read_file", {"path": "/etc/passwd"}, target_agent=server)
|
|
99
|
+
print(f" Result: {result}")
|
|
100
|
+
except Exception as e:
|
|
101
|
+
print(f" ✓ Access denied (as expected): {e}")
|
|
102
|
+
print()
|
|
103
|
+
|
|
104
|
+
print("=" * 50)
|
|
105
|
+
print("Roots enforce filesystem boundaries via MAPL")
|
|
106
|
+
print("=" * 50)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
try:
|
|
111
|
+
main()
|
|
112
|
+
except Exception as e:
|
|
113
|
+
err = str(e)
|
|
114
|
+
print("\n" + "=" * 50)
|
|
115
|
+
if "Connection refused" in err or "connect" in err.lower():
|
|
116
|
+
print("ERROR: Cannot connect to MACAW")
|
|
117
|
+
print("Fix: Ensure MACAW is running")
|
|
118
|
+
else:
|
|
119
|
+
print(f"ERROR: {e}")
|
|
120
|
+
print("=" * 50)
|
|
121
|
+
import sys
|
|
122
|
+
sys.exit(1)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Example 1g: MCP Roots via MAPL - SERVER
|
|
4
|
+
|
|
5
|
+
Demonstrates declaring filesystem roots that constrain server access.
|
|
6
|
+
|
|
7
|
+
Roots are declared via SecureMCP(roots=[...]) and translated to
|
|
8
|
+
MAPL resource policies. Tools use ctx.get_roots() to check boundaries.
|
|
9
|
+
|
|
10
|
+
Run this server, then run 1g_roots_client.py in another terminal.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from macaw_adapters.mcp import SecureMCP, Context
|
|
17
|
+
|
|
18
|
+
# Setup test directory
|
|
19
|
+
DEMO_DIR = "/tmp/securemcp-roots-demo"
|
|
20
|
+
os.makedirs(DEMO_DIR, exist_ok=True)
|
|
21
|
+
(Path(DEMO_DIR) / "allowed.txt").write_text("This file is in an allowed root.")
|
|
22
|
+
|
|
23
|
+
# Create server with declared roots
|
|
24
|
+
mcp = SecureMCP(
|
|
25
|
+
"roots-demo",
|
|
26
|
+
roots=[DEMO_DIR] # Only this directory is accessible
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@mcp.tool(description="List files in directory")
|
|
31
|
+
def list_dir(ctx: Context, path: str) -> dict:
|
|
32
|
+
"""List directory contents (respects root boundaries)."""
|
|
33
|
+
ctx.info(f"Listing: {path}")
|
|
34
|
+
roots = ctx.get_roots()
|
|
35
|
+
path = os.path.abspath(path)
|
|
36
|
+
|
|
37
|
+
# Check boundaries
|
|
38
|
+
if not any(path.startswith(os.path.abspath(r)) for r in roots):
|
|
39
|
+
ctx.warning(f"Access denied: {path}")
|
|
40
|
+
ctx.audit(action="list_dir", target=path, outcome="denied")
|
|
41
|
+
return {"error": "Access denied - outside roots", "allowed_roots": roots}
|
|
42
|
+
|
|
43
|
+
if os.path.exists(path):
|
|
44
|
+
entries = os.listdir(path)
|
|
45
|
+
ctx.audit(action="list_dir", target=path, outcome="success")
|
|
46
|
+
return {"path": path, "entries": entries}
|
|
47
|
+
return {"error": "Path not found"}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@mcp.tool(description="Read a file")
|
|
51
|
+
def read_file(ctx: Context, path: str) -> dict:
|
|
52
|
+
"""Read file contents (respects root boundaries)."""
|
|
53
|
+
ctx.info(f"Reading: {path}")
|
|
54
|
+
roots = ctx.get_roots()
|
|
55
|
+
path = os.path.abspath(path)
|
|
56
|
+
|
|
57
|
+
if not any(path.startswith(os.path.abspath(r)) for r in roots):
|
|
58
|
+
ctx.audit(action="read_file", target=path, outcome="denied")
|
|
59
|
+
return {"error": "Access denied - outside roots"}
|
|
60
|
+
|
|
61
|
+
if os.path.isfile(path):
|
|
62
|
+
content = Path(path).read_text()
|
|
63
|
+
ctx.audit(action="read_file", target=path, outcome="success")
|
|
64
|
+
return {"path": path, "content": content}
|
|
65
|
+
return {"error": "File not found"}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
print("=" * 50)
|
|
70
|
+
print("Example 1g: Roots Demo Server")
|
|
71
|
+
print("=" * 50)
|
|
72
|
+
print()
|
|
73
|
+
print("Tools: list_dir, read_file")
|
|
74
|
+
print()
|
|
75
|
+
print("Declared Roots (via MAPL):")
|
|
76
|
+
for root in mcp.roots:
|
|
77
|
+
print(f" - {root}")
|
|
78
|
+
print()
|
|
79
|
+
print(f"Test file: {DEMO_DIR}/allowed.txt")
|
|
80
|
+
print()
|
|
81
|
+
print("Run client: python3 1g_roots_client.py")
|
|
82
|
+
print()
|
|
83
|
+
print("Press Ctrl+C to stop")
|
|
84
|
+
print("=" * 50)
|
|
85
|
+
mcp.run()
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Example: SecureMCPProxy with Snowflake MCP Server
|
|
4
|
+
|
|
5
|
+
Connect to Snowflake's MCP server with MACAW security for:
|
|
6
|
+
- Cortex AI (search, analyst, agent)
|
|
7
|
+
- SQL execution
|
|
8
|
+
- Object management
|
|
9
|
+
- Semantic views
|
|
10
|
+
|
|
11
|
+
Prerequisites:
|
|
12
|
+
1. Snowflake account (free trial: https://signup.snowflake.com)
|
|
13
|
+
2. Snowflake Labs MCP server running with HTTP transport:
|
|
14
|
+
uvx snowflake-labs-mcp --service-config-file config.yaml --transport streamable-http
|
|
15
|
+
3. MACAW LocalAgent running
|
|
16
|
+
4. Install: pip install macaw-adapters[mcp-proxy]
|
|
17
|
+
|
|
18
|
+
Reference: https://github.com/Snowflake-Labs/mcp
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
# Set environment variables
|
|
22
|
+
export SNOWFLAKE_MCP_URL="http://localhost:9000/snowflake-mcp"
|
|
23
|
+
export SNOWFLAKE_ACCOUNT="your-account"
|
|
24
|
+
|
|
25
|
+
python 2b_proxy_snowflake.py
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import os
|
|
29
|
+
import logging
|
|
30
|
+
|
|
31
|
+
logging.basicConfig(level=logging.INFO)
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def example_snowflake_proxy():
|
|
36
|
+
"""
|
|
37
|
+
Connect to Snowflake MCP server through SecureMCPProxy.
|
|
38
|
+
|
|
39
|
+
Snowflake Labs MCP supports HTTP transport, making it compatible
|
|
40
|
+
with SecureMCPProxy's streamable_http_client.
|
|
41
|
+
"""
|
|
42
|
+
from macaw_adapters.mcp import SecureMCPProxy
|
|
43
|
+
|
|
44
|
+
# Snowflake MCP server URL (running with --transport streamable-http)
|
|
45
|
+
upstream_url = os.environ.get(
|
|
46
|
+
"SNOWFLAKE_MCP_URL",
|
|
47
|
+
"http://localhost:9000/mcp"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Create proxy - connects to Snowflake MCP, wraps with MACAW security
|
|
51
|
+
# Note: Snowflake auth is handled by the MCP server itself via env vars
|
|
52
|
+
# (SNOWFLAKE_USER, SNOWFLAKE_PASSWORD, SNOWFLAKE_ACCOUNT, etc.)
|
|
53
|
+
proxy = SecureMCPProxy(
|
|
54
|
+
app_name="snowflake-mcp",
|
|
55
|
+
upstream_url=upstream_url,
|
|
56
|
+
# No upstream_auth needed - Snowflake MCP server handles auth internally
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
print(f"\n{proxy}")
|
|
60
|
+
|
|
61
|
+
# List discovered tools from Snowflake
|
|
62
|
+
print("\nDiscovered Snowflake tools:")
|
|
63
|
+
for tool in proxy.list_tools():
|
|
64
|
+
print(f" - {tool['name']}: {tool.get('description', '')[:60]}...")
|
|
65
|
+
|
|
66
|
+
# Example: Run SQL query
|
|
67
|
+
try:
|
|
68
|
+
result = proxy.call_tool("run_snowflake_query", {
|
|
69
|
+
"statement": "SELECT CURRENT_USER(), CURRENT_ROLE(), CURRENT_DATABASE()"
|
|
70
|
+
})
|
|
71
|
+
print(f"\nSQL result: {result}")
|
|
72
|
+
except ValueError as e:
|
|
73
|
+
print(f"\nSQL not available: {e}")
|
|
74
|
+
except Exception as e:
|
|
75
|
+
print(f"\nSQL error: {e}")
|
|
76
|
+
|
|
77
|
+
# Example: List databases
|
|
78
|
+
try:
|
|
79
|
+
result = proxy.call_tool("list_objects", {
|
|
80
|
+
"object_type": "database"
|
|
81
|
+
})
|
|
82
|
+
# Result is a dict with 'result' key
|
|
83
|
+
content = result.get('result', result) if isinstance(result, dict) else result
|
|
84
|
+
preview = str(content)[:300] if content else "(empty)"
|
|
85
|
+
print(f"\nList databases result: {preview}...")
|
|
86
|
+
except ValueError as e:
|
|
87
|
+
print(f"\nList objects not available: {e}")
|
|
88
|
+
except Exception as e:
|
|
89
|
+
print(f"\nList objects error: {e}")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def example_snowflake_multi_user():
|
|
93
|
+
"""
|
|
94
|
+
Multi-user pattern: different users query Snowflake with their own policies.
|
|
95
|
+
|
|
96
|
+
Use case: Data platform where analysts have different access levels.
|
|
97
|
+
"""
|
|
98
|
+
from macaw_adapters.mcp import SecureMCPProxy
|
|
99
|
+
from macaw_client import MACAWClient
|
|
100
|
+
|
|
101
|
+
# Service creates shared proxy to Snowflake
|
|
102
|
+
proxy = SecureMCPProxy(
|
|
103
|
+
app_name="snowflake-data-platform",
|
|
104
|
+
upstream_url=os.environ.get("SNOWFLAKE_MCP_URL", "http://localhost:9000/snowflake-mcp"),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Analyst Alice - has access to sales data
|
|
108
|
+
alice = MACAWClient(
|
|
109
|
+
user_name="alice",
|
|
110
|
+
jwt_token=os.environ.get("ALICE_JWT", "alice-jwt"),
|
|
111
|
+
agent_type="user"
|
|
112
|
+
)
|
|
113
|
+
alice.register()
|
|
114
|
+
alice_proxy = proxy.bind_to_user(alice)
|
|
115
|
+
|
|
116
|
+
# Analyst Bob - restricted to aggregate queries only
|
|
117
|
+
bob = MACAWClient(
|
|
118
|
+
user_name="bob",
|
|
119
|
+
jwt_token=os.environ.get("BOB_JWT", "bob-jwt"),
|
|
120
|
+
agent_type="user"
|
|
121
|
+
)
|
|
122
|
+
bob.register()
|
|
123
|
+
bob_proxy = proxy.bind_to_user(bob)
|
|
124
|
+
|
|
125
|
+
print("\nMulti-user Snowflake access:")
|
|
126
|
+
print(f" Alice: {alice_proxy}")
|
|
127
|
+
print(f" Bob: {bob_proxy}")
|
|
128
|
+
|
|
129
|
+
# Alice's query - MACAW policy checked against alice's permissions
|
|
130
|
+
try:
|
|
131
|
+
result = alice_proxy.call_tool("execute_sql", {
|
|
132
|
+
"statement": "SELECT * FROM sales.customers LIMIT 10"
|
|
133
|
+
})
|
|
134
|
+
print(f"\nAlice's query result: {result}")
|
|
135
|
+
except PermissionError as e:
|
|
136
|
+
print(f"\nAlice denied: {e}")
|
|
137
|
+
except Exception as e:
|
|
138
|
+
print(f"\nAlice error: {e}")
|
|
139
|
+
|
|
140
|
+
# Bob's query - MACAW policy checked against bob's permissions
|
|
141
|
+
try:
|
|
142
|
+
result = bob_proxy.call_tool("execute_sql", {
|
|
143
|
+
"statement": "SELECT COUNT(*) FROM sales.customers"
|
|
144
|
+
})
|
|
145
|
+
print(f"\nBob's query result: {result}")
|
|
146
|
+
except PermissionError as e:
|
|
147
|
+
print(f"\nBob denied: {e}")
|
|
148
|
+
except Exception as e:
|
|
149
|
+
print(f"\nBob error: {e}")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def setup_instructions():
|
|
153
|
+
"""Print setup instructions for Snowflake MCP server."""
|
|
154
|
+
print("""
|
|
155
|
+
================================================================================
|
|
156
|
+
Snowflake MCP Server Setup
|
|
157
|
+
================================================================================
|
|
158
|
+
|
|
159
|
+
1. Get Snowflake Account (free 30-day trial):
|
|
160
|
+
https://signup.snowflake.com
|
|
161
|
+
|
|
162
|
+
2. Create config.yaml for Snowflake MCP:
|
|
163
|
+
|
|
164
|
+
# config.yaml
|
|
165
|
+
agent_services: []
|
|
166
|
+
|
|
167
|
+
other_services:
|
|
168
|
+
object_manager: true
|
|
169
|
+
query_manager: true
|
|
170
|
+
semantic_manager: true
|
|
171
|
+
|
|
172
|
+
sql_statement_permissions:
|
|
173
|
+
Select: true
|
|
174
|
+
Create: false
|
|
175
|
+
Drop: false
|
|
176
|
+
|
|
177
|
+
3. Set Snowflake credentials:
|
|
178
|
+
export SNOWFLAKE_ACCOUNT="your-account"
|
|
179
|
+
export SNOWFLAKE_USER="your-user"
|
|
180
|
+
export SNOWFLAKE_PASSWORD="your-password"
|
|
181
|
+
export SNOWFLAKE_WAREHOUSE="COMPUTE_WH"
|
|
182
|
+
export SNOWFLAKE_DATABASE="your-db"
|
|
183
|
+
export SNOWFLAKE_SCHEMA="PUBLIC"
|
|
184
|
+
|
|
185
|
+
4. Run Snowflake MCP server with HTTP transport:
|
|
186
|
+
uvx snowflake-labs-mcp --service-config-file config.yaml --transport streamable-http
|
|
187
|
+
|
|
188
|
+
5. Run this example:
|
|
189
|
+
export SNOWFLAKE_MCP_URL="http://localhost:9000/mcp"
|
|
190
|
+
python 2b_proxy_snowflake.py
|
|
191
|
+
|
|
192
|
+
================================================================================
|
|
193
|
+
""")
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
if __name__ == "__main__":
|
|
197
|
+
print("=" * 60)
|
|
198
|
+
print("SecureMCPProxy + Snowflake MCP Server")
|
|
199
|
+
print("=" * 60)
|
|
200
|
+
|
|
201
|
+
setup_instructions()
|
|
202
|
+
|
|
203
|
+
print("\n--- Example: Basic Snowflake Proxy ---")
|
|
204
|
+
try:
|
|
205
|
+
example_snowflake_proxy()
|
|
206
|
+
except ConnectionError as e:
|
|
207
|
+
print(f"\nConnection failed: {e}")
|
|
208
|
+
print("Make sure Snowflake MCP server is running with HTTP transport.")
|
|
209
|
+
|
|
210
|
+
# Uncomment to test multi-user:
|
|
211
|
+
# print("\n--- Example: Multi-User Snowflake Access ---")
|
|
212
|
+
# example_snowflake_multi_user()
|