@macaw-ai/client 0.0.0-stage → 0.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/LICENSE.md +339 -0
  2. package/bin/install.js +278 -0
  3. package/package.json +37 -4
  4. package/payload/README.md +56 -0
  5. package/payload/secureAI/LICENSE +190 -0
  6. package/payload/secureAI/NOTICE +5 -0
  7. package/payload/secureAI/demos/policy-controls/1_uncontrolled.py +139 -0
  8. package/payload/secureAI/demos/policy-controls/2_with_macaw.py +139 -0
  9. package/payload/secureAI/demos/policy-controls/3_with_user_policies.py +162 -0
  10. package/payload/secureAI/demos/policy-controls/credentials.txt +12 -0
  11. package/payload/secureAI/demos/policy-controls/malicious_report.txt +21 -0
  12. package/payload/secureAI/demos/policy-controls/sales_report.txt +20 -0
  13. package/payload/secureAI/demos/tutorial-1-rbac/README.md +143 -0
  14. package/payload/secureAI/demos/tutorial-1-rbac/app.py +147 -0
  15. package/payload/secureAI/demos/tutorial-1-rbac/authprompts_demo.py +306 -0
  16. package/payload/secureAI/demos/tutorial-1-rbac/config/claims-config.yaml +152 -0
  17. package/payload/secureAI/demos/tutorial-1-rbac/demo_secureopenai_with_policies.py +150 -0
  18. package/payload/secureAI/demos/tutorial-1-rbac/policies/bu_Analytics.json +8 -0
  19. package/payload/secureAI/demos/tutorial-1-rbac/policies/company_FinTech_Corp.json +70 -0
  20. package/payload/secureAI/demos/tutorial-1-rbac/policies/llm-base.json +69 -0
  21. package/payload/secureAI/demos/tutorial-1-rbac/policies/team_Reporting.json +8 -0
  22. package/payload/secureAI/demos/tutorial-1-rbac/policies/user_alice.json +32 -0
  23. package/payload/secureAI/demos/tutorial-1-rbac/policies/user_bob.json +28 -0
  24. package/payload/secureAI/demos/tutorial-1-rbac/policies/user_carol.json +34 -0
  25. package/payload/secureAI/demos/tutorial-1-rbac/requirements.txt +6 -0
  26. package/payload/secureAI/demos/tutorial-1-rbac/setup/auth0_complete_setup.sh +392 -0
  27. package/payload/secureAI/demos/tutorial-1-rbac/setup/keycloak_complete_setup.sh +320 -0
  28. package/payload/secureAI/demos/tutorial-2-intent/Policies/alation-remote-proxy.json +155 -0
  29. package/payload/secureAI/demos/tutorial-2-intent/Policies/bu.json +22 -0
  30. package/payload/secureAI/demos/tutorial-2-intent/Policies/company.json +17 -0
  31. package/payload/secureAI/demos/tutorial-2-intent/Policies/user_alice.json +48 -0
  32. package/payload/secureAI/demos/tutorial-2-intent/Policies/user_bob.json +83 -0
  33. package/payload/secureAI/demos/tutorial-2-intent/README.md +213 -0
  34. package/payload/secureAI/demos/tutorial-2-intent/script.py +188 -0
  35. package/payload/secureAI/demos/tutorial-2-intent/setup/alation_verifier.py +177 -0
  36. package/payload/secureAI/demos/tutorial-2-intent/setup/get_alation_user_token.py +180 -0
  37. package/payload/secureAI/examples/README.md +244 -0
  38. package/payload/secureAI/examples/anthropic/anthropic_1a_dropin_simple.py +103 -0
  39. package/payload/secureAI/examples/anthropic/anthropic_1b_multiuser_bind.py +190 -0
  40. package/payload/secureAI/examples/anthropic/anthropic_1b_multiuser_bind_streaming.py +190 -0
  41. package/payload/secureAI/examples/anthropic/anthropic_1c_a2a_invoke.py +156 -0
  42. package/payload/secureAI/examples/attestations/1a_trade_alice.py +215 -0
  43. package/payload/secureAI/examples/attestations/1a_trade_bob.py +164 -0
  44. package/payload/secureAI/examples/attestations/1b_grant_admin.py +170 -0
  45. package/payload/secureAI/examples/attestations/1b_grant_researcher.py +231 -0
  46. package/payload/secureAI/examples/attestations/README.md +128 -0
  47. package/payload/secureAI/examples/langchain/langchain_1a_dropin_simple.py +199 -0
  48. package/payload/secureAI/examples/langchain/langchain_1b_multiuser.py +262 -0
  49. package/payload/secureAI/examples/langchain/langchain_1c_orchestration.py +332 -0
  50. package/payload/secureAI/examples/langchain/langchain_1d_llm_openai.py +130 -0
  51. package/payload/secureAI/examples/langchain/langchain_1e_llm_anthropic.py +130 -0
  52. package/payload/secureAI/examples/langchain/langchain_1f_memory.py +214 -0
  53. package/payload/secureAI/examples/litellm/litellm_1a_dropin_simple.py +131 -0
  54. package/payload/secureAI/examples/mcp/1a_simple_invocation.py +115 -0
  55. package/payload/secureAI/examples/mcp/1b_discovery_and_resources.py +142 -0
  56. package/payload/secureAI/examples/mcp/1c_logging_client.py +100 -0
  57. package/payload/secureAI/examples/mcp/1d_progress_client.py +100 -0
  58. package/payload/secureAI/examples/mcp/1e_sampling_client.py +150 -0
  59. package/payload/secureAI/examples/mcp/1e_sampling_server.py +90 -0
  60. package/payload/secureAI/examples/mcp/1f_elicitation_client.py +127 -0
  61. package/payload/secureAI/examples/mcp/1f_elicitation_server.py +73 -0
  62. package/payload/secureAI/examples/mcp/1g_roots_client.py +122 -0
  63. package/payload/secureAI/examples/mcp/1g_roots_server.py +85 -0
  64. package/payload/secureAI/examples/mcp/2a_proxy_snowflake.py +212 -0
  65. package/payload/secureAI/examples/mcp/2b_proxy_databricks.py +299 -0
  66. package/payload/secureAI/examples/mcp/2c_proxy_stdio_fetch.py +91 -0
  67. package/payload/secureAI/examples/mcp/2d_proxy_stdio_salesforce.py +119 -0
  68. package/payload/secureAI/examples/mcp/2e_proxy_multiuser.py +136 -0
  69. package/payload/secureAI/examples/mcp/2f_proxy_stdio_databricks.py +144 -0
  70. package/payload/secureAI/examples/mcp/3a_native_mcp_client.py +104 -0
  71. package/payload/secureAI/examples/mcp/3b_native_mcp_proxy.py +114 -0
  72. package/payload/secureAI/examples/mcp/3c_native_mcp_sampling.py +180 -0
  73. package/payload/secureAI/examples/mcp/3d_native_mcp_2026.py +135 -0
  74. package/payload/secureAI/examples/mcp/securemcp_calculator.py +248 -0
  75. package/payload/secureAI/examples/mcp/securemcp_server.py +75 -0
  76. package/payload/secureAI/examples/openai/openai_1a_dropin_simple.py +102 -0
  77. package/payload/secureAI/examples/openai/openai_1b_multiuser_bind.py +193 -0
  78. package/payload/secureAI/examples/openai/openai_1b_multiuser_bind_streaming.py +199 -0
  79. package/payload/secureAI/examples/openai/openai_1c_a2a_invoke.py +156 -0
  80. package/payload/secureAI/examples/openai/openai_1d_invocation_patterns.py +337 -0
  81. package/payload/secureAI/examples/phishing-resistance/1a_login_alice.py +180 -0
  82. package/payload/secureAI/examples/phishing-resistance/1b_trade_alice.py +241 -0
  83. package/payload/secureAI/examples/phishing-resistance/1b_trade_bob.py +164 -0
  84. package/payload/secureAI/examples/phishing-resistance/README.md +124 -0
  85. package/payload/secureAI/examples/phishing-resistance/get_token.py +114 -0
  86. package/payload/secureAI/examples/pydantic_ai/README.md +88 -0
  87. package/payload/secureAI/examples/pydantic_ai/policies/app_pydantic-agent.json +10 -0
  88. package/payload/secureAI/examples/pydantic_ai/pydanticai_1a_dropin_simple.py +90 -0
  89. package/payload/secureAI/examples/pydantic_ai/pydanticai_1b_model_routing.py +85 -0
  90. package/payload/secureAI/examples/pydantic_ai/pydanticai_1c_native_tools.py +83 -0
  91. package/payload/secureAI/examples/pydantic_ai/pydanticai_1d_multiuser_bind.py +104 -0
  92. package/payload/secureAI/examples/pydantic_ai/pydanticai_1e_mcp_compose.py +92 -0
  93. package/payload/secureAI/macaw_adapters/__init__.py +55 -0
  94. package/payload/secureAI/macaw_adapters/anthropic/REFERENCE.md +374 -0
  95. package/payload/secureAI/macaw_adapters/anthropic/__init__.py +22 -0
  96. package/payload/secureAI/macaw_adapters/anthropic/secure_anthropic.py +792 -0
  97. package/payload/secureAI/macaw_adapters/anthropic_ext_tools/__init__.py +25 -0
  98. package/payload/secureAI/macaw_adapters/anthropic_ext_tools/secure_anthropic.py +840 -0
  99. package/payload/secureAI/macaw_adapters/langchain/REFERENCE.md +234 -0
  100. package/payload/secureAI/macaw_adapters/langchain/__init__.py +100 -0
  101. package/payload/secureAI/macaw_adapters/langchain/_utils.py +83 -0
  102. package/payload/secureAI/macaw_adapters/langchain/agents.py +296 -0
  103. package/payload/secureAI/macaw_adapters/langchain/anthropic.py +456 -0
  104. package/payload/secureAI/macaw_adapters/langchain/callbacks.py +306 -0
  105. package/payload/secureAI/macaw_adapters/langchain/memory.py +352 -0
  106. package/payload/secureAI/macaw_adapters/langchain/openai.py +410 -0
  107. package/payload/secureAI/macaw_adapters/langchain/tools.py +174 -0
  108. package/payload/secureAI/macaw_adapters/litellm/README.md +216 -0
  109. package/payload/secureAI/macaw_adapters/litellm/__init__.py +143 -0
  110. package/payload/secureAI/macaw_adapters/litellm/secure_litellm.py +686 -0
  111. package/payload/secureAI/macaw_adapters/mcp/REFERENCE.md +460 -0
  112. package/payload/secureAI/macaw_adapters/mcp/__init__.py +65 -0
  113. package/payload/secureAI/macaw_adapters/mcp/_endpoint.py +193 -0
  114. package/payload/secureAI/macaw_adapters/mcp/_endpoint2.py +225 -0
  115. package/payload/secureAI/macaw_adapters/mcp/_serve.py +25 -0
  116. package/payload/secureAI/macaw_adapters/mcp/client.py +721 -0
  117. package/payload/secureAI/macaw_adapters/mcp/decorators.py +228 -0
  118. package/payload/secureAI/macaw_adapters/mcp/fastmcp_compat.py +353 -0
  119. package/payload/secureAI/macaw_adapters/mcp/mcp.py +785 -0
  120. package/payload/secureAI/macaw_adapters/mcp/proxy.py +626 -0
  121. package/payload/secureAI/macaw_adapters/mcp/server.py +317 -0
  122. package/payload/secureAI/macaw_adapters/mcp/subscriptions.py +450 -0
  123. package/payload/secureAI/macaw_adapters/openai/REFERENCE.md +368 -0
  124. package/payload/secureAI/macaw_adapters/openai/__init__.py +22 -0
  125. package/payload/secureAI/macaw_adapters/openai/secure_openai.py +833 -0
  126. package/payload/secureAI/macaw_adapters/openai_ext_tools/REFERENCE.md +368 -0
  127. package/payload/secureAI/macaw_adapters/openai_ext_tools/__init__.py +25 -0
  128. package/payload/secureAI/macaw_adapters/openai_ext_tools/secure_openai.py +876 -0
  129. package/payload/secureAI/macaw_adapters/pydantic_ai/__init__.py +36 -0
  130. package/payload/secureAI/macaw_adapters/pydantic_ai/_core.py +257 -0
  131. package/payload/secureAI/macaw_adapters/pydantic_ai/agent.py +218 -0
  132. package/payload/secureAI/macaw_adapters/pydantic_ai/model.py +99 -0
  133. package/payload/secureAI/macaw_adapters/pydantic_ai/toolset.py +86 -0
  134. package/payload/secureAI/test_harness_quick.py +296 -0
  135. package/wheels/macaw_adapters-0.9.10.1-py3-none-any.whl +0 -0
  136. package/wheels/macaw_client-0.9.10.1-cp312-cp312-macosx_11_0_arm64.whl +0 -0
  137. package/wheels/macaw_client-0.9.10.1-cp312-cp312-manylinux_2_17_x86_64.whl +0 -0
  138. package/wheels/macaw_client-0.9.10.1-cp314-cp314-macosx_11_0_arm64.whl +0 -0
  139. package/wheels/macaw_client-0.9.10.1-cp314-cp314-manylinux_2_17_x86_64.whl +0 -0
  140. package/wheels/macaw_client-0.9.10.1-cp314-cp314-win_amd64.whl +0 -0
  141. package/wheels/macaw_client-0.9.10.1-cp39-cp39-macosx_11_0_arm64.whl +0 -0
  142. package/README.md +0 -3
@@ -0,0 +1,228 @@
1
+ """
2
+ SecureMCP @secure Decorator - Simplified Version
3
+ Delegates all security to MACAW SDK components from server instance
4
+ """
5
+
6
+ import threading
7
+ from functools import wraps
8
+ from typing import Dict, Any, Optional, List, Callable, Union
9
+
10
+ # Thread-local storage for current server instance
11
+ _current_server = threading.local()
12
+
13
+ def set_current_server(server):
14
+ """Set the current server in thread-local storage"""
15
+ _current_server.value = server
16
+
17
+ def get_current_server():
18
+ """Get the current server from thread-local storage"""
19
+ return getattr(_current_server, 'value', None)
20
+
21
+
22
+ def secure(
23
+ # Core security parameters
24
+ policy: Union[str, Dict[str, Any]] = None,
25
+ policy_file: Optional[str] = None,
26
+ audit_log: bool = False,
27
+ audit_level: str = "basic",
28
+
29
+ # Authentication
30
+ auth_provider: Optional[str] = None,
31
+ required_roles: Optional[List[str]] = None,
32
+ required_groups: Optional[List[str]] = None,
33
+
34
+ # Rate limiting
35
+ rate_limit: Optional[int] = None,
36
+
37
+ # Signing
38
+ sign_requests: bool = False,
39
+ sign_results: bool = False,
40
+
41
+ # Monitoring
42
+ monitor: bool = False,
43
+ trace: bool = False,
44
+
45
+ # Advanced features (for future use)
46
+ attested: bool = False,
47
+ tenant_aware: bool = False,
48
+ risk_assessment: Optional[Dict[str, Any]] = None,
49
+ llm_security: Optional[Dict[str, Any]] = None,
50
+ **kwargs # Accept other params for compatibility
51
+ ):
52
+ """
53
+ Optional security enhancement decorator for MCP tools.
54
+
55
+ This decorator adds fine-grained security controls on top of the baseline
56
+ security provided by SecureMCP's transport layer. All security enforcement
57
+ is delegated to MACAW SDK components.
58
+
59
+ Args:
60
+ policy: Security policy name or dict
61
+ policy_file: Path to policy file
62
+ audit_log: Enable detailed audit logging
63
+ audit_level: Level of audit detail (basic/detailed/full)
64
+ auth_provider: Authentication provider (okta/github/auth0/etc)
65
+ required_roles: List of required roles
66
+ required_groups: List of required groups
67
+ rate_limit: Calls per minute limit
68
+ sign_requests: Sign incoming requests
69
+ sign_results: Sign outgoing results
70
+ monitor: Enable monitoring
71
+ trace: Enable tracing
72
+ attested: Require attested workflow
73
+ tenant_aware: Enable multi-tenancy
74
+ risk_assessment: Risk assessment config
75
+ llm_security: LLM security config
76
+ **kwargs: Additional parameters for future expansion
77
+
78
+ Example:
79
+ @server.tool("sensitive_operation")
80
+ @secure(
81
+ auth_provider="okta",
82
+ required_roles=["admin"],
83
+ audit_log=True,
84
+ rate_limit=10
85
+ )
86
+ def sensitive_operation(data: str) -> str:
87
+ return process_data(data)
88
+ """
89
+ def decorator(func: Callable) -> Callable:
90
+ @wraps(func)
91
+ def wrapper(*args, **kwargs):
92
+ # Get server instance
93
+ server = get_current_server()
94
+ if not server:
95
+ # No server context, just run the function
96
+ return func(*args, **kwargs)
97
+
98
+ # Get MACAW components from server
99
+ policy_enforcer = getattr(server, 'policy_enforcer', None)
100
+ audit_logger = getattr(server, 'audit_logger', None)
101
+ server_agent = getattr(server, 'server_agent', None)
102
+
103
+ # Build security context
104
+ security_context = {
105
+ 'function': func.__name__,
106
+ 'policy': policy,
107
+ 'auth_provider': auth_provider,
108
+ 'required_roles': required_roles,
109
+ 'required_groups': required_groups,
110
+ 'rate_limit': rate_limit,
111
+ 'attested': attested
112
+ }
113
+
114
+ # 1. Authentication checks (delegated to MACAW)
115
+ if auth_provider or required_roles or required_groups:
116
+ # In production, MACAW's AuthenticationService handles this
117
+ # For now, we'll check if server has auth configured
118
+ if not getattr(server, 'request_authenticator', None):
119
+ # Log warning but don't block
120
+ if audit_logger:
121
+ audit_logger.log_event({
122
+ 'type': 'auth_check_skipped',
123
+ 'reason': 'no_authenticator',
124
+ 'function': func.__name__
125
+ })
126
+
127
+ # 2. Policy enforcement (delegated to MACAW)
128
+ if policy and policy_enforcer:
129
+ # MACAW's PolicyEnforcer handles this
130
+ # Create an invocation context
131
+ from macaw.protocol.mcp_types import Invocation
132
+ invocation = Invocation(
133
+ tool_name=func.__name__,
134
+ params=kwargs,
135
+ metadata=security_context
136
+ )
137
+
138
+ # Check policy
139
+ try:
140
+ allowed = policy_enforcer.check_invocation(invocation)
141
+ if not allowed:
142
+ return {
143
+ 'error': 'Policy violation',
144
+ 'policy': policy,
145
+ 'function': func.__name__
146
+ }
147
+ except Exception as e:
148
+ # Policy check failed, log but don't block
149
+ if audit_logger:
150
+ audit_logger.log_event({
151
+ 'type': 'policy_check_error',
152
+ 'error': str(e),
153
+ 'function': func.__name__
154
+ })
155
+
156
+ # 3. Rate limiting (delegated to MACAW)
157
+ if rate_limit:
158
+ # MACAW's RateLimitVerifier handles this
159
+ # For now, just log the intent
160
+ if audit_logger:
161
+ audit_logger.log_event({
162
+ 'type': 'rate_limit_check',
163
+ 'limit': rate_limit,
164
+ 'function': func.__name__
165
+ })
166
+
167
+ # 4. Pre-execution audit
168
+ if audit_log and audit_logger:
169
+ audit_logger.log_event({
170
+ 'type': 'function_invocation',
171
+ 'function': func.__name__,
172
+ 'audit_level': audit_level,
173
+ 'security_context': security_context,
174
+ 'args_count': len(args),
175
+ 'kwargs_keys': list(kwargs.keys())
176
+ })
177
+
178
+ # 5. Sign request if needed
179
+ if sign_requests and server_agent:
180
+ # MACAW's agent handles signing
181
+ # This would be done at transport layer
182
+ pass
183
+
184
+ # 6. Execute the function
185
+ try:
186
+ result = func(*args, **kwargs)
187
+ except Exception as e:
188
+ # Log error and re-raise
189
+ if audit_logger:
190
+ audit_logger.log_event({
191
+ 'type': 'function_error',
192
+ 'function': func.__name__,
193
+ 'error': str(e)
194
+ })
195
+ raise
196
+
197
+ # 7. Sign result if needed
198
+ if sign_results and server_agent:
199
+ # MACAW's agent handles signing
200
+ # This would be done at transport layer
201
+ pass
202
+
203
+ # 8. Post-execution audit
204
+ if audit_log and audit_logger:
205
+ audit_logger.log_event({
206
+ 'type': 'function_completed',
207
+ 'function': func.__name__,
208
+ 'success': True
209
+ })
210
+
211
+ # 9. Monitoring/tracing
212
+ if (monitor or trace) and hasattr(server, 'telemetry_manager'):
213
+ # MACAW's telemetry handles this
214
+ pass
215
+
216
+ return result
217
+
218
+ # Mark function as secure-decorated for introspection
219
+ wrapper._secure_decorated = True
220
+ wrapper._secure_params = {
221
+ 'policy': policy,
222
+ 'auth_provider': auth_provider,
223
+ 'required_roles': required_roles,
224
+ 'audit_log': audit_log
225
+ }
226
+
227
+ return wrapper
228
+ return decorator
@@ -0,0 +1,353 @@
1
+ """
2
+ FastMCP Compatibility Layer for SecureMCP
3
+
4
+ Provides FastMCP-style features and API compatibility to make migration easier.
5
+ This allows developers familiar with FastMCP to use SecureMCP with minimal changes.
6
+ """
7
+
8
+ import asyncio
9
+ import inspect
10
+ import logging
11
+ from typing import Optional, Dict, Any, Callable, Type, get_type_hints, Union
12
+ from contextlib import asynccontextmanager
13
+ from dataclasses import dataclass
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class TypeValidator:
19
+ """
20
+ Automatic type validation for tool parameters.
21
+
22
+ FastMCP feature: Validates parameters against type hints automatically.
23
+ """
24
+
25
+ @staticmethod
26
+ def validate_params(func: Callable, params: Dict[str, Any]) -> Dict[str, Any]:
27
+ """
28
+ Validate and coerce parameters based on function type hints.
29
+
30
+ Args:
31
+ func: Function with type hints
32
+ params: Parameters to validate
33
+
34
+ Returns:
35
+ Validated and coerced parameters
36
+
37
+ Raises:
38
+ TypeError: If validation fails
39
+ """
40
+ # Get type hints
41
+ hints = get_type_hints(func)
42
+ sig = inspect.signature(func)
43
+
44
+ validated = {}
45
+
46
+ for param_name, param_info in sig.parameters.items():
47
+ if param_name == "self":
48
+ continue
49
+
50
+ # Get value from params or use default
51
+ if param_name in params:
52
+ value = params[param_name]
53
+ elif param_info.default != inspect.Parameter.empty:
54
+ value = param_info.default
55
+ else:
56
+ raise TypeError(f"Missing required parameter: {param_name}")
57
+
58
+ # Get expected type
59
+ if param_name in hints:
60
+ expected_type = hints[param_name]
61
+
62
+ # Handle Optional types
63
+ if hasattr(expected_type, '__origin__') and expected_type.__origin__ is Union:
64
+ # This is Optional[T] or Union[T, None]
65
+ if value is not None:
66
+ # Get the non-None type
67
+ types = expected_type.__args__
68
+ expected_type = next(t for t in types if t != type(None))
69
+ else:
70
+ validated[param_name] = None
71
+ continue
72
+
73
+ # Try to coerce type
74
+ if not isinstance(value, expected_type):
75
+ try:
76
+ if expected_type == int:
77
+ value = int(value)
78
+ elif expected_type == float:
79
+ value = float(value)
80
+ elif expected_type == str:
81
+ value = str(value)
82
+ elif expected_type == bool:
83
+ value = bool(value)
84
+ else:
85
+ raise TypeError(f"Cannot coerce {value} to {expected_type}")
86
+ except (ValueError, TypeError) as e:
87
+ raise TypeError(f"Parameter {param_name}: expected {expected_type.__name__}, got {type(value).__name__}")
88
+
89
+ validated[param_name] = value
90
+
91
+ return validated
92
+
93
+
94
+ class FastMCPServer:
95
+ """
96
+ FastMCP-compatible server wrapper.
97
+
98
+ Adds FastMCP-style features on top of SecureMCP Server.
99
+ """
100
+
101
+ def __init__(self, server):
102
+ """
103
+ Wrap a SecureMCP server with FastMCP compatibility.
104
+
105
+ Args:
106
+ server: SecureMCP Server instance
107
+ """
108
+ self.server = server
109
+ self._original_tool = server.tool
110
+
111
+ # Override tool decorator to add type validation
112
+ server.tool = self.tool
113
+
114
+ logger.info("FastMCP compatibility layer initialized")
115
+
116
+ def tool(self, name: Optional[str] = None, description: str = ""):
117
+ """
118
+ Enhanced tool decorator with automatic type validation.
119
+
120
+ FastMCP feature: Automatically validates types based on hints.
121
+ """
122
+ def decorator(func: Callable) -> Callable:
123
+ # Get the original function for type hints
124
+ original_func = func
125
+
126
+ # Create wrapper that validates types
127
+ def validated_wrapper(**kwargs):
128
+ # Validate and coerce types
129
+ validated_params = TypeValidator.validate_params(original_func, kwargs)
130
+
131
+ # Call original function with validated params
132
+ return original_func(**validated_params)
133
+
134
+ # Copy metadata
135
+ validated_wrapper.__name__ = original_func.__name__
136
+ validated_wrapper.__doc__ = original_func.__doc__
137
+
138
+ # Register with server using original decorator
139
+ return self._original_tool(name, description)(validated_wrapper)
140
+
141
+ return decorator
142
+
143
+ async def call_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Any:
144
+ """
145
+ Call a tool programmatically (dependency injection).
146
+
147
+ FastMCP feature: Allows tools to call other tools.
148
+
149
+ Args:
150
+ tool_name: Name of tool to call
151
+ arguments: Tool arguments
152
+
153
+ Returns:
154
+ Tool result
155
+ """
156
+ if tool_name not in self.server.tools:
157
+ raise ValueError(f"Unknown tool: {tool_name}")
158
+
159
+ tool_info = self.server.tools[tool_name]
160
+ function = tool_info["function"]
161
+
162
+ # Call the tool
163
+ result = function(**arguments)
164
+
165
+ # Handle async functions
166
+ if asyncio.iscoroutine(result):
167
+ result = await result
168
+
169
+ return result
170
+
171
+ @asynccontextmanager
172
+ async def test_client(self):
173
+ """
174
+ Create a test client for the server.
175
+
176
+ FastMCP feature: Easy testing with context manager.
177
+
178
+ Usage:
179
+ async with server.test_client() as client:
180
+ result = await client.call_tool("my-tool", {"arg": "value"})
181
+ """
182
+ from . import Client
183
+
184
+ # Create a test client
185
+ client = Client(f"test-client-{self.server.name}")
186
+
187
+ # Create a mock connection (in-process)
188
+ class TestConnection:
189
+ def __init__(self, server):
190
+ self.server = server
191
+
192
+ async def call_tool(self, name: str, args: Dict[str, Any]) -> Any:
193
+ """Direct in-process tool call"""
194
+ return await self.server.call_tool(name, args)
195
+
196
+ async def get_resource(self, uri: str) -> Any:
197
+ """Direct in-process resource read"""
198
+ if uri in self.server.server.resources:
199
+ resource_info = self.server.server.resources[uri]
200
+ resource_class = resource_info["class"]
201
+ instance = resource_class(uri)
202
+ if hasattr(instance, 'read'):
203
+ return await instance.read()
204
+ return None
205
+
206
+ async def get_prompt(self, name: str, args: Dict[str, Any]) -> str:
207
+ """Direct in-process prompt generation"""
208
+ if name in self.server.server.prompts:
209
+ prompt_info = self.server.server.prompts[name]
210
+ function = prompt_info["function"]
211
+ return function(**args)
212
+ return ""
213
+
214
+ # Attach test connection to client
215
+ test_conn = TestConnection(self)
216
+ client.call_tool = test_conn.call_tool
217
+ client.get_resource = test_conn.get_resource
218
+ client.get_prompt = test_conn.get_prompt
219
+ client.connected = True
220
+
221
+ try:
222
+ yield client
223
+ finally:
224
+ # Cleanup
225
+ pass
226
+
227
+
228
+ @dataclass
229
+ class ErrorTemplate:
230
+ """
231
+ Standardized error response template.
232
+
233
+ FastMCP feature: Consistent error formatting.
234
+ """
235
+ code: str
236
+ message: str
237
+ details: Optional[Dict[str, Any]] = None
238
+
239
+ def to_dict(self) -> Dict[str, Any]:
240
+ """Convert to MCP error format"""
241
+ result = {
242
+ "error": {
243
+ "code": self.code,
244
+ "message": self.message
245
+ }
246
+ }
247
+ if self.details:
248
+ result["error"]["details"] = self.details
249
+ return result
250
+
251
+
252
+ class Pagination:
253
+ """
254
+ Pagination helper for resources.
255
+
256
+ FastMCP feature: Built-in pagination support.
257
+ """
258
+
259
+ @staticmethod
260
+ def paginate(
261
+ items: list,
262
+ page: int = 1,
263
+ page_size: int = 10
264
+ ) -> Dict[str, Any]:
265
+ """
266
+ Paginate a list of items.
267
+
268
+ Args:
269
+ items: List to paginate
270
+ page: Page number (1-based)
271
+ page_size: Items per page
272
+
273
+ Returns:
274
+ Paginated response with metadata
275
+ """
276
+ total = len(items)
277
+ total_pages = (total + page_size - 1) // page_size
278
+
279
+ # Calculate slice
280
+ start = (page - 1) * page_size
281
+ end = start + page_size
282
+
283
+ return {
284
+ "items": items[start:end],
285
+ "pagination": {
286
+ "page": page,
287
+ "page_size": page_size,
288
+ "total": total,
289
+ "total_pages": total_pages,
290
+ "has_next": page < total_pages,
291
+ "has_prev": page > 1
292
+ }
293
+ }
294
+
295
+
296
+ def add_fastmcp_compat(server):
297
+ """
298
+ Add FastMCP compatibility to a SecureMCP server.
299
+
300
+ Usage:
301
+ from macaw_adapters.mcp import Server
302
+ from macaw_adapters.mcp.fastmcp_compat import add_fastmcp_compat
303
+
304
+ server = Server("my-server")
305
+ server = add_fastmcp_compat(server)
306
+
307
+ # Now use FastMCP features
308
+ @server.tool("typed-tool")
309
+ def my_tool(path: str, count: int = 10):
310
+ # Types are automatically validated!
311
+ pass
312
+
313
+ Args:
314
+ server: SecureMCP Server instance
315
+
316
+ Returns:
317
+ Server with FastMCP compatibility
318
+ """
319
+ compat = FastMCPServer(server)
320
+
321
+ # Add methods to server
322
+ server.call_tool = compat.call_tool
323
+ server.test_client = compat.test_client
324
+
325
+ # Add helper classes
326
+ server.ErrorTemplate = ErrorTemplate
327
+ server.Pagination = Pagination
328
+
329
+ return server
330
+
331
+
332
+ # Convenience decorators for standalone use
333
+ def typed_tool(name: Optional[str] = None, description: str = ""):
334
+ """
335
+ Standalone typed tool decorator.
336
+
337
+ Usage:
338
+ @typed_tool("calculate")
339
+ def calculate(a: float, b: float) -> float:
340
+ return a + b
341
+ """
342
+ def decorator(func: Callable) -> Callable:
343
+ # Create wrapper that validates types
344
+ def validated_wrapper(**kwargs):
345
+ validated_params = TypeValidator.validate_params(func, kwargs)
346
+ return func(**validated_params)
347
+
348
+ validated_wrapper.__name__ = func.__name__
349
+ validated_wrapper.__doc__ = func.__doc__
350
+
351
+ return validated_wrapper
352
+
353
+ return decorator