@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,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Per-user policy on a shared agent.
|
|
3
|
+
|
|
4
|
+
One SecureAgent is built once for the service. bind_to_user() returns an agent
|
|
5
|
+
that issues every invocation as that user, so MACAW evaluates alice's and bob's
|
|
6
|
+
policies against the same application resources.
|
|
7
|
+
|
|
8
|
+
service = SecureAgent(model, app_name="pydantic-agent", tools=[...])
|
|
9
|
+
alice_agent = service.bind_to_user(alice_client)
|
|
10
|
+
|
|
11
|
+
The registration and the tools are shared; only the caller identity differs.
|
|
12
|
+
|
|
13
|
+
Prerequisites:
|
|
14
|
+
export OPENAI_API_KEY=sk-...
|
|
15
|
+
MACAW LocalAgent running
|
|
16
|
+
Identity provider running with the demo users (Keycloak or Auth0)
|
|
17
|
+
Workspace policies for user:alice and user:bob covering tool:pydantic-agent/*
|
|
18
|
+
|
|
19
|
+
Run:
|
|
20
|
+
python pydanticai_1d_multiuser_bind.py
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
from macaw_client import MACAWClient, PermissionDenied, RemoteIdentityProvider
|
|
27
|
+
from pydantic_ai.models.openai import OpenAIChatModel
|
|
28
|
+
|
|
29
|
+
from macaw_adapters.pydantic_ai import SecureAgent
|
|
30
|
+
|
|
31
|
+
APP_NAME = "pydantic-agent"
|
|
32
|
+
GENERATE = f"tool:{APP_NAME}/generate"
|
|
33
|
+
|
|
34
|
+
USERS = [("alice", "Alice123!"), ("bob", "Bob@123!")]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def query_catalog(table: str, limit: int = 10) -> str:
|
|
38
|
+
"""Query the data catalog for rows from a table."""
|
|
39
|
+
return f"{limit} rows from {table}"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def export_dataset(table: str) -> str:
|
|
43
|
+
"""Export a full dataset to external storage."""
|
|
44
|
+
return f"exported {table}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def login(username: str, password: str):
|
|
48
|
+
"""Authenticate against the identity provider and register a user agent."""
|
|
49
|
+
jwt_token, _ = RemoteIdentityProvider().login(username, password)
|
|
50
|
+
user = MACAWClient(
|
|
51
|
+
user_name=username,
|
|
52
|
+
iam_token=jwt_token,
|
|
53
|
+
agent_type="user",
|
|
54
|
+
app_name=APP_NAME,
|
|
55
|
+
)
|
|
56
|
+
if not user.register():
|
|
57
|
+
raise RuntimeError(f"Failed to register user agent for {username}")
|
|
58
|
+
return user
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def main() -> int:
|
|
62
|
+
if not os.environ.get("OPENAI_API_KEY"):
|
|
63
|
+
print("Set OPENAI_API_KEY first:")
|
|
64
|
+
print(" export OPENAI_API_KEY=sk-...")
|
|
65
|
+
return 1
|
|
66
|
+
|
|
67
|
+
# One service agent, shared by every user.
|
|
68
|
+
service = SecureAgent(
|
|
69
|
+
OpenAIChatModel("gpt-4o-mini"),
|
|
70
|
+
app_name=APP_NAME,
|
|
71
|
+
tools=[query_catalog, export_dataset],
|
|
72
|
+
intent_policy={"resources": [GENERATE, f"tool:{APP_NAME}/*"]},
|
|
73
|
+
)
|
|
74
|
+
print(f"Service registered: {service._macaw.server_id}")
|
|
75
|
+
|
|
76
|
+
for username, password in USERS:
|
|
77
|
+
print(f"\n=== {username} ===")
|
|
78
|
+
try:
|
|
79
|
+
user = login(username, password)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
print(f" login failed: {e}")
|
|
82
|
+
print(" Is the identity provider running? See demos/tutorial-1/setup/")
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
agent = service.bind_to_user(user)
|
|
86
|
+
|
|
87
|
+
for prompt in (
|
|
88
|
+
"Use query_catalog on the customers table with limit 3.",
|
|
89
|
+
"Export the customers table using export_dataset.",
|
|
90
|
+
):
|
|
91
|
+
try:
|
|
92
|
+
result = agent.run_sync(prompt)
|
|
93
|
+
print(f" {result.output}")
|
|
94
|
+
except PermissionDenied as e:
|
|
95
|
+
print(f" denied by MACAW: {e}")
|
|
96
|
+
|
|
97
|
+
user.unregister()
|
|
98
|
+
|
|
99
|
+
print("\nSame agent, same tools. The difference is whose policy was resolved.")
|
|
100
|
+
return 0
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if __name__ == "__main__":
|
|
104
|
+
sys.exit(main())
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Local tools and an MCP server in one agent.
|
|
3
|
+
|
|
4
|
+
A Pydantic AI agent can mix tools it owns with tools reached over MCP. Both are
|
|
5
|
+
governed, but at different boundaries:
|
|
6
|
+
|
|
7
|
+
local functions registered with MACAW and dispatched by it
|
|
8
|
+
-> tool:pydantic-agent/<name>
|
|
9
|
+
|
|
10
|
+
MCP tools authorized here under the caller's identity, then executed
|
|
11
|
+
by the server, which SecureMCP governs on its own side
|
|
12
|
+
-> tool:pydantic-agent/<name> (this agent)
|
|
13
|
+
-> tool:securemcp-calculator/<name> (the server)
|
|
14
|
+
|
|
15
|
+
The agent-side check is the one that carries the user's identity, because an MCP
|
|
16
|
+
connection has none of its own.
|
|
17
|
+
|
|
18
|
+
This example runs examples/mcp/securemcp_calculator.py as a subprocess over
|
|
19
|
+
stdio, the same way examples/mcp/3a_native_mcp_client.py does.
|
|
20
|
+
|
|
21
|
+
Prerequisites:
|
|
22
|
+
export OPENAI_API_KEY=sk-...
|
|
23
|
+
MACAW LocalAgent running
|
|
24
|
+
Workspace policy allowing tool:pydantic-agent/*
|
|
25
|
+
|
|
26
|
+
Run:
|
|
27
|
+
python pydanticai_1e_mcp_compose.py
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import asyncio
|
|
31
|
+
import os
|
|
32
|
+
import sys
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
|
|
35
|
+
from pydantic_ai.mcp import MCPToolset, StdioTransport
|
|
36
|
+
from pydantic_ai.models.openai import OpenAIChatModel
|
|
37
|
+
|
|
38
|
+
from macaw_adapters.pydantic_ai import SecureAgent
|
|
39
|
+
|
|
40
|
+
APP_NAME = "pydantic-agent"
|
|
41
|
+
GENERATE = f"tool:{APP_NAME}/generate"
|
|
42
|
+
|
|
43
|
+
CALCULATOR = Path(__file__).resolve().parents[1] / "mcp" / "securemcp_calculator.py"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def describe_result(value: str) -> str:
|
|
47
|
+
"""Write a one-line description of a calculation result."""
|
|
48
|
+
return f"result recorded: {value}"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def main() -> int:
|
|
52
|
+
if not os.environ.get("OPENAI_API_KEY"):
|
|
53
|
+
print("Set OPENAI_API_KEY first:")
|
|
54
|
+
print(" export OPENAI_API_KEY=sk-...")
|
|
55
|
+
return 1
|
|
56
|
+
|
|
57
|
+
if not CALCULATOR.exists():
|
|
58
|
+
print(f"Calculator server not found: {CALCULATOR}")
|
|
59
|
+
return 1
|
|
60
|
+
|
|
61
|
+
# env is passed through so the server subprocess can import macaw_adapters
|
|
62
|
+
# when running from a source checkout rather than an installed package.
|
|
63
|
+
calculator = MCPToolset(
|
|
64
|
+
StdioTransport(
|
|
65
|
+
command=sys.executable,
|
|
66
|
+
args=[str(CALCULATOR), "stdio"],
|
|
67
|
+
env=dict(os.environ),
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
agent = SecureAgent(
|
|
72
|
+
OpenAIChatModel("gpt-4o-mini"),
|
|
73
|
+
app_name=APP_NAME,
|
|
74
|
+
tools=[describe_result], # owned: MACAW dispatches these
|
|
75
|
+
toolsets=[calculator], # MCP: MACAW authorizes, server executes
|
|
76
|
+
intent_policy={"resources": [GENERATE, f"tool:{APP_NAME}/*"]},
|
|
77
|
+
)
|
|
78
|
+
print(f"Registered: {agent._macaw.server_id}")
|
|
79
|
+
print(f"Local tools: describe_result")
|
|
80
|
+
print(f"MCP server : {CALCULATOR.name}")
|
|
81
|
+
|
|
82
|
+
result = await agent.run(
|
|
83
|
+
"Add 17 and 25 using the calculator, then describe the result."
|
|
84
|
+
)
|
|
85
|
+
print(f"\n{result.output}")
|
|
86
|
+
print("\nBoth tools were invoked through MACAW under this agent's identity.")
|
|
87
|
+
|
|
88
|
+
return 0
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
sys.exit(asyncio.run(main()))
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MACAW Adapters - Secure AI Adapters for Enterprise
|
|
3
|
+
|
|
4
|
+
Security adapters for popular AI frameworks including OpenAI, Anthropic,
|
|
5
|
+
LangChain, LiteLLM (100+ providers), and MCP (Model Context Protocol).
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from macaw_adapters.openai import SecureOpenAI
|
|
9
|
+
from macaw_adapters.anthropic import SecureAnthropic
|
|
10
|
+
from macaw_adapters.langchain.agents import create_react_agent, AgentExecutor
|
|
11
|
+
from macaw_adapters.mcp import SecureMCP
|
|
12
|
+
|
|
13
|
+
# LiteLLM - drop-in replacement supporting 100+ providers
|
|
14
|
+
from macaw_adapters import litellm
|
|
15
|
+
response = litellm.completion(model="groq/llama3-70b", messages=[...])
|
|
16
|
+
|
|
17
|
+
Prerequisites:
|
|
18
|
+
- MACAW Client Library: Download from https://macawsecurity.ai
|
|
19
|
+
- Free Account: Create at https://console.macawsecurity.ai
|
|
20
|
+
|
|
21
|
+
For more information, visit: https://macawsecurity.ai
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
__version__ = "0.9.10.1"
|
|
25
|
+
__author__ = "MACAW Security"
|
|
26
|
+
__license__ = "Apache-2.0"
|
|
27
|
+
|
|
28
|
+
# Lazy imports - only load adapters when explicitly imported
|
|
29
|
+
# This allows using one adapter without installing dependencies for others
|
|
30
|
+
# e.g., `from macaw_adapters.mcp import SecureMCP` works without openai installed
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"openai",
|
|
34
|
+
"anthropic",
|
|
35
|
+
"langchain",
|
|
36
|
+
"litellm",
|
|
37
|
+
"mcp",
|
|
38
|
+
"pydantic_ai",
|
|
39
|
+
"__version__",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def __getattr__(name):
|
|
44
|
+
"""
|
|
45
|
+
Lazy import adapters only when accessed.
|
|
46
|
+
|
|
47
|
+
Imported by full module path: an adapter shares its name with the package it
|
|
48
|
+
wraps (openai, mcp, pydantic_ai, ...), and `from macaw_adapters import <name>`
|
|
49
|
+
falls back to this function for those, which recurses.
|
|
50
|
+
"""
|
|
51
|
+
if name in ("openai", "anthropic", "langchain", "litellm", "mcp", "pydantic_ai"):
|
|
52
|
+
import importlib
|
|
53
|
+
|
|
54
|
+
return importlib.import_module(f"macaw_adapters.{name}")
|
|
55
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# SecureAnthropic Reference
|
|
2
|
+
|
|
3
|
+
Drop-in replacement for Anthropic Claude client with MACAW security. Supports three usage paths for different scenarios.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from macaw_adapters.anthropic import SecureAnthropic
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Install the package:
|
|
12
|
+
```bash
|
|
13
|
+
pip install -e /path/to/secureAI
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## When to Use Each Path
|
|
19
|
+
|
|
20
|
+
| Scenario | Path | Example |
|
|
21
|
+
|----------|------|---------|
|
|
22
|
+
| Simple app, no user distinction | Direct on service | `client.messages.create()` |
|
|
23
|
+
| Multi-user, per-user policies | bind_to_user | `service.bind_to_user(user_client)` |
|
|
24
|
+
| A2A communication, explicit control | invoke_tool | `user.invoke_tool("tool:xxx/generate", ...)` |
|
|
25
|
+
| CLI tool, single operator | Direct on service | `client.messages.create()` |
|
|
26
|
+
| SaaS with user accounts | bind_to_user | Different users = different permissions |
|
|
27
|
+
|
|
28
|
+
### Why bind_to_user?
|
|
29
|
+
|
|
30
|
+
Without `bind_to_user`, everyone uses the **service's identity**. With `bind_to_user`, each user's **JWT identity flows through** for policy evaluation.
|
|
31
|
+
|
|
32
|
+
This enables scenarios like:
|
|
33
|
+
- Alice: Claude Haiku only, max 500 tokens
|
|
34
|
+
- Bob: Claude Haiku/Sonnet/Opus, max 2000 tokens
|
|
35
|
+
|
|
36
|
+
Same service, different permissions based on WHO is calling.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Path 1: Direct on Service (Simplest)
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from macaw_adapters.anthropic import SecureAnthropic
|
|
44
|
+
|
|
45
|
+
# Just like using regular Anthropic client
|
|
46
|
+
client = SecureAnthropic(
|
|
47
|
+
app_name="my-app",
|
|
48
|
+
intent_policy={"purpose": "financial analysis"}
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Same API as Anthropic
|
|
52
|
+
response = client.messages.create(
|
|
53
|
+
model="claude-3-opus-20240229",
|
|
54
|
+
max_tokens=500,
|
|
55
|
+
messages=[{"role": "user", "content": "What is compound interest?"}]
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
print(response.content[0].text)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Use when**: Single app, no user distinction, app-level policies.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Path 2: bind_to_user (Multi-user)
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from macaw_adapters.anthropic import SecureAnthropic
|
|
69
|
+
from macaw_client import MACAWClient, RemoteIdentityProvider
|
|
70
|
+
|
|
71
|
+
# 1. Create SINGLE service (shared across all users)
|
|
72
|
+
service = SecureAnthropic(app_name="financial-service")
|
|
73
|
+
|
|
74
|
+
# 2. User login -> JWT -> MACAWClient
|
|
75
|
+
jwt_token, _ = RemoteIdentityProvider().login("alice", "Alice123!")
|
|
76
|
+
user = MACAWClient(
|
|
77
|
+
user_name="alice",
|
|
78
|
+
iam_token=jwt_token,
|
|
79
|
+
agent_type="user",
|
|
80
|
+
app_name="financial-app"
|
|
81
|
+
)
|
|
82
|
+
user.register()
|
|
83
|
+
|
|
84
|
+
# 3. Bind user to service
|
|
85
|
+
user_anthropic = service.bind_to_user(user)
|
|
86
|
+
|
|
87
|
+
# 4. Same API, user's identity flows through for policy evaluation
|
|
88
|
+
response = user_anthropic.messages.create(
|
|
89
|
+
model="claude-3-haiku-20240307",
|
|
90
|
+
max_tokens=500,
|
|
91
|
+
messages=[{"role": "user", "content": "Hello"}]
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# 5. Cleanup when user session ends
|
|
95
|
+
user_anthropic.unbind()
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Use when**: SaaS app, different users need different permissions.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Path 3: invoke_tool (A2A)
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from macaw_adapters.anthropic import SecureAnthropic
|
|
106
|
+
from macaw_client import MACAWClient, RemoteIdentityProvider
|
|
107
|
+
|
|
108
|
+
# 1. Create Anthropic service
|
|
109
|
+
service = SecureAnthropic(app_name="anthropic-service")
|
|
110
|
+
|
|
111
|
+
# 2. Create user agent
|
|
112
|
+
jwt_token, _ = RemoteIdentityProvider().login("alice", "Alice123!")
|
|
113
|
+
user = MACAWClient(
|
|
114
|
+
user_name="alice",
|
|
115
|
+
iam_token=jwt_token,
|
|
116
|
+
agent_type="user",
|
|
117
|
+
app_name="my-agent"
|
|
118
|
+
)
|
|
119
|
+
user.register()
|
|
120
|
+
|
|
121
|
+
# 3. Explicit invoke_tool - you control everything
|
|
122
|
+
result = user.invoke_tool(
|
|
123
|
+
tool_name="tool:anthropic-service/generate", # MAPL tool name
|
|
124
|
+
parameters={
|
|
125
|
+
"model": "claude-3-haiku-20240307",
|
|
126
|
+
"max_tokens": 500,
|
|
127
|
+
"messages": [{"role": "user", "content": "What is compound interest?"}]
|
|
128
|
+
},
|
|
129
|
+
target_agent=service.server_id # Explicit routing
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Result is raw dict
|
|
133
|
+
content = result["content"][0]["text"]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Use when**: Building agent systems, need explicit control, cross-service communication.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## SecureAnthropic Class
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
client = SecureAnthropic(
|
|
144
|
+
api_key="sk-ant-...", # Optional: uses ANTHROPIC_API_KEY env var
|
|
145
|
+
app_name="my-app", # Application name for MACAW registration
|
|
146
|
+
intent_policy={...}, # Application-defined security policy (MAPL format)
|
|
147
|
+
jwt_token="...", # Optional: creates user-mode client
|
|
148
|
+
user_name="alice" # Optional: user name for user mode
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Constructor Parameters
|
|
153
|
+
|
|
154
|
+
| Parameter | Type | Default | Description |
|
|
155
|
+
|-----------|------|---------|-------------|
|
|
156
|
+
| `api_key` | str | env var | Anthropic API key |
|
|
157
|
+
| `app_name` | str | `"secure-anthropic-app"` | Application name for MACAW |
|
|
158
|
+
| `intent_policy` | dict | `{}` | Security policy (MAPL format) |
|
|
159
|
+
| `jwt_token` | str | None | JWT token for user mode |
|
|
160
|
+
| `user_name` | str | None | User name for user mode |
|
|
161
|
+
|
|
162
|
+
### Properties
|
|
163
|
+
|
|
164
|
+
| Property | Type | Description |
|
|
165
|
+
|----------|------|-------------|
|
|
166
|
+
| `client.server_id` | str | Agent ID after registration |
|
|
167
|
+
| `client.macaw_client` | MACAWClient | MACAW client instance |
|
|
168
|
+
| `client.anthropic_client` | Anthropic | Underlying Anthropic client |
|
|
169
|
+
| `client._mode` | str | "service" or "user" |
|
|
170
|
+
|
|
171
|
+
### Methods
|
|
172
|
+
|
|
173
|
+
#### bind_to_user(user_client) -> BoundSecureAnthropic
|
|
174
|
+
|
|
175
|
+
Bind service to a user's MACAW client for per-user identity.
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
user_anthropic = service.bind_to_user(user_client)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Only valid in service mode. Returns `BoundSecureAnthropic` wrapper.
|
|
182
|
+
|
|
183
|
+
#### register_tool(name, handler) -> SecureAnthropic
|
|
184
|
+
|
|
185
|
+
Register a tool that Claude can call.
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
client.register_tool("get_weather", get_weather_fn)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## BoundSecureAnthropic Class
|
|
194
|
+
|
|
195
|
+
Per-user wrapper created via `bind_to_user()`.
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
user_anthropic = service.bind_to_user(user_client)
|
|
199
|
+
|
|
200
|
+
# Same API as SecureAnthropic
|
|
201
|
+
response = user_anthropic.messages.create(...)
|
|
202
|
+
|
|
203
|
+
# Check binding status
|
|
204
|
+
if user_anthropic.is_bound:
|
|
205
|
+
...
|
|
206
|
+
|
|
207
|
+
# Cleanup
|
|
208
|
+
user_anthropic.unbind()
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Properties
|
|
212
|
+
|
|
213
|
+
| Property | Type | Description |
|
|
214
|
+
|----------|------|-------------|
|
|
215
|
+
| `is_bound` | bool | Whether wrapper is still bound |
|
|
216
|
+
| `service` | SecureAnthropic | The bound service (raises if unbound) |
|
|
217
|
+
| `user_client` | MACAWClient | The bound user client (raises if unbound) |
|
|
218
|
+
|
|
219
|
+
### Methods
|
|
220
|
+
|
|
221
|
+
#### unbind()
|
|
222
|
+
|
|
223
|
+
Invalidate this binding. All future calls will raise `RuntimeError`.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## API Surface
|
|
228
|
+
|
|
229
|
+
SecureAnthropic mirrors the Anthropic API structure:
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
# Messages API (primary)
|
|
233
|
+
response = client.messages.create(
|
|
234
|
+
model="claude-3-opus-20240229",
|
|
235
|
+
max_tokens=1024,
|
|
236
|
+
messages=[{"role": "user", "content": "..."}],
|
|
237
|
+
tools=[...] # Optional: auto-discovered
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Completions API (legacy)
|
|
241
|
+
response = client.completions.create(
|
|
242
|
+
model="claude-2.1",
|
|
243
|
+
prompt="..."
|
|
244
|
+
)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## MAPL Tool Names
|
|
250
|
+
|
|
251
|
+
SecureAnthropic uses MAPL-compliant tool names:
|
|
252
|
+
|
|
253
|
+
| Operation | Tool Name |
|
|
254
|
+
|-----------|-----------|
|
|
255
|
+
| Messages | `tool:{app_name}/generate` |
|
|
256
|
+
| Completions | `tool:{app_name}/complete` |
|
|
257
|
+
|
|
258
|
+
Example: If `app_name="anthropic-service"`, the generate tool is `tool:anthropic-service/generate`.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Tool Auto-Discovery
|
|
263
|
+
|
|
264
|
+
SecureAnthropic automatically discovers tool implementations from the caller's scope:
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
# Define tools in your code
|
|
268
|
+
def get_weather(location: str) -> str:
|
|
269
|
+
return f"Weather in {location}: Sunny, 72F"
|
|
270
|
+
|
|
271
|
+
# Define tool schemas for Claude
|
|
272
|
+
tools = [
|
|
273
|
+
{
|
|
274
|
+
"name": "get_weather", # Must match function name
|
|
275
|
+
"description": "Get weather for a location",
|
|
276
|
+
"input_schema": {
|
|
277
|
+
"type": "object",
|
|
278
|
+
"properties": {
|
|
279
|
+
"location": {"type": "string", "description": "City name"}
|
|
280
|
+
},
|
|
281
|
+
"required": ["location"]
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
]
|
|
285
|
+
|
|
286
|
+
# Tools are auto-discovered and routed through MACAW PEP
|
|
287
|
+
response = client.messages.create(
|
|
288
|
+
model="claude-3-opus-20240229",
|
|
289
|
+
max_tokens=1024,
|
|
290
|
+
messages=[{"role": "user", "content": "What's the weather in Paris?"}],
|
|
291
|
+
tools=tools
|
|
292
|
+
)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Intent Policy Format (MAPL)
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
intent_policy = {
|
|
301
|
+
# Deny access to sensitive resources
|
|
302
|
+
"denied_resources": ["*password*", "*credential*", "*secret*"],
|
|
303
|
+
|
|
304
|
+
# Allow specific resources
|
|
305
|
+
"resources": ["tool:get_weather", "tool:search_files"],
|
|
306
|
+
|
|
307
|
+
# Parameter constraints
|
|
308
|
+
"constraints": {
|
|
309
|
+
"parameters": {
|
|
310
|
+
"tool:search_files": {
|
|
311
|
+
"path": ["*public*", "*reports*"]
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
"denied_parameters": {
|
|
315
|
+
"tool:*": {
|
|
316
|
+
"query": ["*password*"]
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
# Application metadata
|
|
322
|
+
"purpose": "weather and file search app"
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Authenticated Prompts
|
|
329
|
+
|
|
330
|
+
SecureAnthropic supports authenticated prompts for cryptographic proof of prompt origin:
|
|
331
|
+
|
|
332
|
+
```python
|
|
333
|
+
# Tools declare which parameters are prompts
|
|
334
|
+
self.tools = {
|
|
335
|
+
f"tool:{self.app_name}/generate": {
|
|
336
|
+
"handler": self._handle_generate,
|
|
337
|
+
"prompts": ["messages"] # messages param is authenticated
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Authenticated prompts are auto-created when `agent.authenticate_prompts` is enabled.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Migration from Anthropic
|
|
347
|
+
|
|
348
|
+
```python
|
|
349
|
+
# Before
|
|
350
|
+
from anthropic import Anthropic
|
|
351
|
+
client = Anthropic()
|
|
352
|
+
|
|
353
|
+
# After (drop-in replacement)
|
|
354
|
+
from macaw_adapters.anthropic import SecureAnthropic
|
|
355
|
+
client = SecureAnthropic(
|
|
356
|
+
app_name="my-app",
|
|
357
|
+
intent_policy={"denied_resources": ["*sensitive*"]}
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# Same API - no other code changes needed
|
|
361
|
+
response = client.messages.create(...)
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Key Differences from OpenAI Adapter
|
|
367
|
+
|
|
368
|
+
| Feature | SecureAnthropic | SecureOpenAI |
|
|
369
|
+
|---------|-----------------|--------------|
|
|
370
|
+
| Tool Schema | `input_schema` | `parameters` in `function` |
|
|
371
|
+
| Tool Type | Direct name | `type: "function"` wrapper |
|
|
372
|
+
| Response | `content` blocks | `message.tool_calls` |
|
|
373
|
+
| Continuation | `tool_result` in user message | `tool` role message |
|
|
374
|
+
| Primary API | `messages.create()` | `chat.completions.create()` |
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MACAW Anthropic Adapter - Secure drop-in replacement for Anthropic client.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
# Before
|
|
6
|
+
from anthropic import Anthropic
|
|
7
|
+
client = Anthropic()
|
|
8
|
+
|
|
9
|
+
# After (one line change!)
|
|
10
|
+
from macaw_adapters.anthropic import SecureAnthropic
|
|
11
|
+
client = SecureAnthropic()
|
|
12
|
+
|
|
13
|
+
Features:
|
|
14
|
+
- Drop-in replacement for Anthropic client
|
|
15
|
+
- Policy-enforced tool execution
|
|
16
|
+
- Cryptographic audit trail
|
|
17
|
+
- Multi-user identity binding
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from macaw_adapters.anthropic.secure_anthropic import SecureAnthropic
|
|
21
|
+
|
|
22
|
+
__all__ = ["SecureAnthropic"]
|