traia-iatp 0.1.29__py3-none-any.whl
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.
Potentially problematic release.
This version of traia-iatp might be problematic. Click here for more details.
- traia_iatp/README.md +368 -0
- traia_iatp/__init__.py +54 -0
- traia_iatp/cli/__init__.py +5 -0
- traia_iatp/cli/main.py +483 -0
- traia_iatp/client/__init__.py +10 -0
- traia_iatp/client/a2a_client.py +274 -0
- traia_iatp/client/crewai_a2a_tools.py +335 -0
- traia_iatp/client/d402_a2a_client.py +293 -0
- traia_iatp/client/grpc_a2a_tools.py +349 -0
- traia_iatp/client/root_path_a2a_client.py +1 -0
- traia_iatp/contracts/__init__.py +12 -0
- traia_iatp/contracts/iatp_contracts_config.py +263 -0
- traia_iatp/contracts/wallet_creator.py +255 -0
- traia_iatp/core/__init__.py +43 -0
- traia_iatp/core/models.py +172 -0
- traia_iatp/d402/__init__.py +55 -0
- traia_iatp/d402/chains.py +102 -0
- traia_iatp/d402/client.py +150 -0
- traia_iatp/d402/clients/__init__.py +7 -0
- traia_iatp/d402/clients/base.py +218 -0
- traia_iatp/d402/clients/httpx.py +219 -0
- traia_iatp/d402/common.py +114 -0
- traia_iatp/d402/encoding.py +28 -0
- traia_iatp/d402/examples/client_example.py +197 -0
- traia_iatp/d402/examples/server_example.py +171 -0
- traia_iatp/d402/facilitator.py +453 -0
- traia_iatp/d402/fastapi_middleware/__init__.py +6 -0
- traia_iatp/d402/fastapi_middleware/middleware.py +225 -0
- traia_iatp/d402/fastmcp_middleware.py +147 -0
- traia_iatp/d402/mcp_middleware.py +434 -0
- traia_iatp/d402/middleware.py +193 -0
- traia_iatp/d402/models.py +116 -0
- traia_iatp/d402/networks.py +98 -0
- traia_iatp/d402/path.py +43 -0
- traia_iatp/d402/payment_introspection.py +104 -0
- traia_iatp/d402/payment_signing.py +178 -0
- traia_iatp/d402/paywall.py +119 -0
- traia_iatp/d402/starlette_middleware.py +326 -0
- traia_iatp/d402/template.py +1 -0
- traia_iatp/d402/types.py +300 -0
- traia_iatp/mcp/__init__.py +18 -0
- traia_iatp/mcp/client.py +201 -0
- traia_iatp/mcp/d402_mcp_tool_adapter.py +361 -0
- traia_iatp/mcp/mcp_agent_template.py +481 -0
- traia_iatp/mcp/templates/Dockerfile.j2 +80 -0
- traia_iatp/mcp/templates/README.md.j2 +310 -0
- traia_iatp/mcp/templates/cursor-rules.md.j2 +520 -0
- traia_iatp/mcp/templates/deployment_params.json.j2 +20 -0
- traia_iatp/mcp/templates/docker-compose.yml.j2 +32 -0
- traia_iatp/mcp/templates/dockerignore.j2 +47 -0
- traia_iatp/mcp/templates/env.example.j2 +57 -0
- traia_iatp/mcp/templates/gitignore.j2 +77 -0
- traia_iatp/mcp/templates/mcp_health_check.py.j2 +150 -0
- traia_iatp/mcp/templates/pyproject.toml.j2 +32 -0
- traia_iatp/mcp/templates/pyrightconfig.json.j2 +22 -0
- traia_iatp/mcp/templates/run_local_docker.sh.j2 +390 -0
- traia_iatp/mcp/templates/server.py.j2 +175 -0
- traia_iatp/mcp/traia_mcp_adapter.py +543 -0
- traia_iatp/preview_diagrams.html +181 -0
- traia_iatp/registry/__init__.py +26 -0
- traia_iatp/registry/atlas_search_indexes.json +280 -0
- traia_iatp/registry/embeddings.py +298 -0
- traia_iatp/registry/iatp_search_api.py +846 -0
- traia_iatp/registry/mongodb_registry.py +771 -0
- traia_iatp/registry/readmes/ATLAS_SEARCH_INDEXES.md +252 -0
- traia_iatp/registry/readmes/ATLAS_SEARCH_SETUP.md +134 -0
- traia_iatp/registry/readmes/AUTHENTICATION_UPDATE.md +124 -0
- traia_iatp/registry/readmes/EMBEDDINGS_SETUP.md +172 -0
- traia_iatp/registry/readmes/IATP_SEARCH_API_GUIDE.md +257 -0
- traia_iatp/registry/readmes/MONGODB_X509_AUTH.md +208 -0
- traia_iatp/registry/readmes/README.md +251 -0
- traia_iatp/registry/readmes/REFACTORING_SUMMARY.md +191 -0
- traia_iatp/scripts/__init__.py +2 -0
- traia_iatp/scripts/create_wallet.py +244 -0
- traia_iatp/server/__init__.py +15 -0
- traia_iatp/server/a2a_server.py +219 -0
- traia_iatp/server/example_template_usage.py +72 -0
- traia_iatp/server/iatp_server_agent_generator.py +237 -0
- traia_iatp/server/iatp_server_template_generator.py +235 -0
- traia_iatp/server/templates/.dockerignore.j2 +48 -0
- traia_iatp/server/templates/Dockerfile.j2 +49 -0
- traia_iatp/server/templates/README.md +137 -0
- traia_iatp/server/templates/README.md.j2 +425 -0
- traia_iatp/server/templates/__init__.py +1 -0
- traia_iatp/server/templates/__main__.py.j2 +565 -0
- traia_iatp/server/templates/agent.py.j2 +94 -0
- traia_iatp/server/templates/agent_config.json.j2 +22 -0
- traia_iatp/server/templates/agent_executor.py.j2 +279 -0
- traia_iatp/server/templates/docker-compose.yml.j2 +23 -0
- traia_iatp/server/templates/env.example.j2 +84 -0
- traia_iatp/server/templates/gitignore.j2 +78 -0
- traia_iatp/server/templates/grpc_server.py.j2 +218 -0
- traia_iatp/server/templates/pyproject.toml.j2 +78 -0
- traia_iatp/server/templates/run_local_docker.sh.j2 +103 -0
- traia_iatp/server/templates/server.py.j2 +243 -0
- traia_iatp/special_agencies/__init__.py +4 -0
- traia_iatp/special_agencies/registry_search_agency.py +392 -0
- traia_iatp/utils/__init__.py +10 -0
- traia_iatp/utils/docker_utils.py +251 -0
- traia_iatp/utils/general.py +64 -0
- traia_iatp/utils/iatp_utils.py +126 -0
- traia_iatp-0.1.29.dist-info/METADATA +423 -0
- traia_iatp-0.1.29.dist-info/RECORD +107 -0
- traia_iatp-0.1.29.dist-info/WHEEL +5 -0
- traia_iatp-0.1.29.dist-info/entry_points.txt +2 -0
- traia_iatp-0.1.29.dist-info/licenses/LICENSE +21 -0
- traia_iatp-0.1.29.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Traia MCP Adapter
|
|
4
|
+
|
|
5
|
+
A custom MCP adapter that extends CrewAI's MCPServerAdapter to support
|
|
6
|
+
passing headers (like Authorization) and x402 payment protocol to streamable-http MCP servers.
|
|
7
|
+
|
|
8
|
+
This adapter transparently handles:
|
|
9
|
+
- Authenticated connections (via headers)
|
|
10
|
+
- Payment-based connections (via x402/d402 protocol)
|
|
11
|
+
- Standard connections (no auth or payment)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from typing import Dict, Any, List, Optional, Union
|
|
16
|
+
import httpx
|
|
17
|
+
from functools import wraps
|
|
18
|
+
|
|
19
|
+
from crewai_tools import MCPServerAdapter
|
|
20
|
+
from crewai.tools import BaseTool
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
# Try to import d402 payment hooks from IATP
|
|
25
|
+
try:
|
|
26
|
+
from eth_account import Account
|
|
27
|
+
from traia_iatp.d402.clients.httpx import d402_payment_hooks
|
|
28
|
+
D402_AVAILABLE = True
|
|
29
|
+
except ImportError:
|
|
30
|
+
D402_AVAILABLE = False
|
|
31
|
+
logger.warning("d402 payment hooks not available. Ensure traia_iatp.d402 is installed.")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TraiaMCPAdapter(MCPServerAdapter):
|
|
35
|
+
"""
|
|
36
|
+
MCP adapter that supports custom headers for streamable-http transport.
|
|
37
|
+
|
|
38
|
+
This adapter extracts headers from server_params and injects them into
|
|
39
|
+
all HTTP requests made to the MCP server. It works transparently for
|
|
40
|
+
both authenticated and non-authenticated connections.
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
```python
|
|
44
|
+
# With authentication headers
|
|
45
|
+
server_params = {
|
|
46
|
+
"url": "https://mcp.example.com/mcp",
|
|
47
|
+
"transport": "streamable-http",
|
|
48
|
+
"headers": {"Authorization": "Bearer YOUR_API_KEY"}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Without headers (standard connection)
|
|
52
|
+
server_params = {
|
|
53
|
+
"url": "https://mcp.example.com/mcp",
|
|
54
|
+
"transport": "streamable-http"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
with TraiaMCPAdapter(server_params) as tools:
|
|
58
|
+
# Use tools with or without authentication
|
|
59
|
+
agent = Agent(tools=tools)
|
|
60
|
+
```
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
server_params: Union[Dict[str, Any], Any],
|
|
66
|
+
*tool_names: str
|
|
67
|
+
):
|
|
68
|
+
"""
|
|
69
|
+
Initialize the adapter with optional header and x402 payment support.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
server_params: Server configuration. For streamable-http, can include:
|
|
73
|
+
- url: The MCP server URL
|
|
74
|
+
- transport: "streamable-http"
|
|
75
|
+
- headers: Optional dict of headers to include in requests
|
|
76
|
+
- x402_account: Optional eth_account.Account for x402 payments
|
|
77
|
+
- x402_max_value: Optional max payment value
|
|
78
|
+
*tool_names: Optional tool names to filter
|
|
79
|
+
"""
|
|
80
|
+
# Handle dict params
|
|
81
|
+
if isinstance(server_params, dict):
|
|
82
|
+
# Extract headers and d402 config if present (don't modify original)
|
|
83
|
+
server_params_copy = server_params.copy()
|
|
84
|
+
self._auth_headers = server_params_copy.pop("headers", {})
|
|
85
|
+
self._d402_account = server_params_copy.pop("d402_account", None)
|
|
86
|
+
self._d402_max_value = server_params_copy.pop("d402_max_value", None)
|
|
87
|
+
|
|
88
|
+
# IMPORTANT: Also remove any x402_* parameters that shouldn't go to MCP library
|
|
89
|
+
# The MCP library's streamablehttp_client() doesn't accept these
|
|
90
|
+
server_params_copy.pop("x402_account", None)
|
|
91
|
+
server_params_copy.pop("x402_max_value", None)
|
|
92
|
+
|
|
93
|
+
# Determine connection mode
|
|
94
|
+
if self._d402_account and D402_AVAILABLE:
|
|
95
|
+
logger.info("💳 TraiaMCPAdapter: d402 payment protocol enabled")
|
|
96
|
+
logger.info(f" Client account: {self._d402_account.address}")
|
|
97
|
+
logger.info(f" Max value: {self._d402_max_value}")
|
|
98
|
+
logger.info(" Applying httpx monkey-patch for d402 hooks...")
|
|
99
|
+
# Apply d402 payment hooks patch
|
|
100
|
+
self._apply_d402_patch()
|
|
101
|
+
logger.info(" ✅ Monkey-patch applied - ALL new httpx clients will have d402 hooks")
|
|
102
|
+
elif self._auth_headers:
|
|
103
|
+
logger.info(f"TraiaMCPAdapter: Headers configured: {list(self._auth_headers.keys())}")
|
|
104
|
+
# Apply header injection patch
|
|
105
|
+
self._apply_httpx_patch()
|
|
106
|
+
|
|
107
|
+
# Pass clean params to parent - this will create the MCP client
|
|
108
|
+
logger.info(" Creating parent MCPServerAdapter...")
|
|
109
|
+
super().__init__(server_params_copy, *tool_names)
|
|
110
|
+
logger.info(" ✅ Parent MCPServerAdapter created")
|
|
111
|
+
else:
|
|
112
|
+
# Non-dict params, no headers or payment possible
|
|
113
|
+
self._auth_headers = {}
|
|
114
|
+
self._d402_account = None
|
|
115
|
+
self._d402_max_value = None
|
|
116
|
+
super().__init__(server_params, *tool_names)
|
|
117
|
+
|
|
118
|
+
def _apply_httpx_patch(self):
|
|
119
|
+
"""Monkey-patch httpx.AsyncClient to inject headers."""
|
|
120
|
+
original_init = httpx.AsyncClient.__init__
|
|
121
|
+
auth_headers = self._auth_headers
|
|
122
|
+
|
|
123
|
+
@wraps(original_init)
|
|
124
|
+
def patched_init(client_self, *args, **kwargs):
|
|
125
|
+
# Get existing headers
|
|
126
|
+
existing_headers = kwargs.get('headers', {})
|
|
127
|
+
if isinstance(existing_headers, dict):
|
|
128
|
+
# Merge our headers
|
|
129
|
+
merged_headers = {**auth_headers, **existing_headers}
|
|
130
|
+
kwargs['headers'] = merged_headers
|
|
131
|
+
logger.debug(f"Injected headers into httpx.AsyncClient: {list(auth_headers.keys())}")
|
|
132
|
+
|
|
133
|
+
# Also ensure headers are preserved on redirects
|
|
134
|
+
if 'follow_redirects' not in kwargs:
|
|
135
|
+
kwargs['follow_redirects'] = True
|
|
136
|
+
|
|
137
|
+
# Call original init
|
|
138
|
+
original_init(client_self, *args, **kwargs)
|
|
139
|
+
|
|
140
|
+
# Apply the patch
|
|
141
|
+
httpx.AsyncClient.__init__ = patched_init
|
|
142
|
+
self._original_httpx_init = original_init
|
|
143
|
+
logger.debug("Applied httpx.AsyncClient monkey patch for headers")
|
|
144
|
+
|
|
145
|
+
def _apply_d402_patch(self):
|
|
146
|
+
"""Monkey-patch httpx.AsyncClient to add d402 payment hooks."""
|
|
147
|
+
if not D402_AVAILABLE:
|
|
148
|
+
raise ImportError("d402 payment hooks not available. Ensure traia_iatp.d402 is installed.")
|
|
149
|
+
|
|
150
|
+
original_init = httpx.AsyncClient.__init__
|
|
151
|
+
d402_account = self._d402_account # Keep variable name for compatibility
|
|
152
|
+
d402_max_value = self._d402_max_value
|
|
153
|
+
|
|
154
|
+
@wraps(original_init)
|
|
155
|
+
def patched_init(client_self, *args, **kwargs):
|
|
156
|
+
# Log client creation
|
|
157
|
+
import traceback
|
|
158
|
+
creation_stack = ''.join(traceback.format_stack()[-4:-1])
|
|
159
|
+
logger.info(f"🔨 httpx.AsyncClient.__init__ called")
|
|
160
|
+
logger.debug(f" Creation stack: {creation_stack}")
|
|
161
|
+
|
|
162
|
+
# Call original init first
|
|
163
|
+
original_init(client_self, *args, **kwargs)
|
|
164
|
+
|
|
165
|
+
# Add d402 payment hooks to the client
|
|
166
|
+
if d402_account:
|
|
167
|
+
try:
|
|
168
|
+
hooks = d402_payment_hooks(d402_account, max_value=d402_max_value)
|
|
169
|
+
# Merge with existing hooks if any
|
|
170
|
+
if hasattr(client_self, 'event_hooks') and client_self.event_hooks:
|
|
171
|
+
# Merge hooks
|
|
172
|
+
for event_type, handlers in hooks.items():
|
|
173
|
+
if event_type in client_self.event_hooks:
|
|
174
|
+
client_self.event_hooks[event_type].extend(handlers)
|
|
175
|
+
else:
|
|
176
|
+
client_self.event_hooks[event_type] = handlers
|
|
177
|
+
else:
|
|
178
|
+
client_self.event_hooks = hooks
|
|
179
|
+
|
|
180
|
+
logger.info(f"✅ Applied d402 payment hooks to httpx.AsyncClient")
|
|
181
|
+
logger.info(f" Account: {d402_account.address[:10]}...")
|
|
182
|
+
logger.info(f" Hooks: {list(hooks.keys())}")
|
|
183
|
+
except Exception as e:
|
|
184
|
+
logger.error(f"❌ Failed to apply d402 hooks: {e}")
|
|
185
|
+
import traceback
|
|
186
|
+
logger.error(traceback.format_exc())
|
|
187
|
+
raise
|
|
188
|
+
|
|
189
|
+
# Apply the patch
|
|
190
|
+
httpx.AsyncClient.__init__ = patched_init
|
|
191
|
+
self._original_httpx_init = original_init
|
|
192
|
+
logger.debug("Applied httpx.AsyncClient monkey patch for d402 payments")
|
|
193
|
+
|
|
194
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
195
|
+
"""Exit context manager and restore original httpx if patched."""
|
|
196
|
+
# Restore original httpx.AsyncClient.__init__ if we patched it
|
|
197
|
+
if hasattr(self, '_original_httpx_init'):
|
|
198
|
+
httpx.AsyncClient.__init__ = self._original_httpx_init
|
|
199
|
+
logger.debug("Restored original httpx.AsyncClient")
|
|
200
|
+
|
|
201
|
+
# Call parent exit
|
|
202
|
+
return super().__exit__(exc_type, exc_val, exc_tb)
|
|
203
|
+
|
|
204
|
+
def __enter__(self) -> List[BaseTool]:
|
|
205
|
+
"""Enter context manager."""
|
|
206
|
+
if self._d402_account:
|
|
207
|
+
logger.debug(f"TraiaMCPAdapter: Using d402 payment connection")
|
|
208
|
+
elif self._auth_headers:
|
|
209
|
+
logger.debug(f"TraiaMCPAdapter: Using authenticated connection")
|
|
210
|
+
|
|
211
|
+
return super().__enter__()
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def create_mcp_adapter(
|
|
215
|
+
url: str,
|
|
216
|
+
headers: Optional[Dict[str, str]] = None,
|
|
217
|
+
transport: str = "streamable-http",
|
|
218
|
+
tool_names: Optional[List[str]] = None,
|
|
219
|
+
x402_account: Optional[Any] = None,
|
|
220
|
+
x402_max_value: Optional[int] = None
|
|
221
|
+
) -> TraiaMCPAdapter:
|
|
222
|
+
"""
|
|
223
|
+
Create a Traia MCP adapter with optional headers and/or d402 payment support.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
url: MCP server URL
|
|
227
|
+
headers: Optional dictionary of headers to include in requests
|
|
228
|
+
transport: Transport type (default: streamable-http)
|
|
229
|
+
tool_names: Optional list of tool names to filter
|
|
230
|
+
x402_account: Optional eth_account.Account for d402 payment protocol
|
|
231
|
+
x402_max_value: Optional maximum payment value in base units.
|
|
232
|
+
Typically, each MCP server uses one primary token, so this limit
|
|
233
|
+
applies to all endpoints using that token. Set based on the token's
|
|
234
|
+
base units (e.g., USDC with 6 decimals: $1.00 = 1_000_000).
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
TraiaMCPAdapter configured with headers and/or d402 payments
|
|
238
|
+
|
|
239
|
+
Example:
|
|
240
|
+
```python
|
|
241
|
+
# With headers (authenticated)
|
|
242
|
+
adapter = create_mcp_adapter(
|
|
243
|
+
url="https://news-mcp.example.com/mcp",
|
|
244
|
+
headers={"Authorization": "Bearer YOUR_API_KEY"}
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# With d402 payments
|
|
248
|
+
from eth_account import Account
|
|
249
|
+
account = Account.from_key("0x...")
|
|
250
|
+
adapter = create_mcp_adapter(
|
|
251
|
+
url="https://news-mcp.example.com/mcp",
|
|
252
|
+
x402_account=account,
|
|
253
|
+
x402_max_value=1_000_000 # $1.00 in USDC (6 decimals)
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Without headers or payment (standard)
|
|
257
|
+
adapter = create_mcp_adapter(
|
|
258
|
+
url="https://news-mcp.example.com/mcp"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
with adapter as tools:
|
|
262
|
+
agent = Agent(tools=tools)
|
|
263
|
+
```
|
|
264
|
+
"""
|
|
265
|
+
server_params = {
|
|
266
|
+
"url": url,
|
|
267
|
+
"transport": transport
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if headers:
|
|
271
|
+
server_params["headers"] = headers
|
|
272
|
+
|
|
273
|
+
if x402_account:
|
|
274
|
+
server_params["d402_account"] = x402_account # Use d402_ internally
|
|
275
|
+
if x402_max_value is not None:
|
|
276
|
+
server_params["d402_max_value"] = x402_max_value # Use d402_ internally
|
|
277
|
+
|
|
278
|
+
if tool_names:
|
|
279
|
+
return TraiaMCPAdapter(server_params, *tool_names)
|
|
280
|
+
else:
|
|
281
|
+
return TraiaMCPAdapter(server_params)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def create_mcp_adapter_with_auth(
|
|
285
|
+
url: str,
|
|
286
|
+
api_key: str,
|
|
287
|
+
auth_header: str = "Authorization",
|
|
288
|
+
auth_prefix: str = "Bearer",
|
|
289
|
+
transport: str = "streamable-http",
|
|
290
|
+
tool_names: Optional[List[str]] = None
|
|
291
|
+
) -> TraiaMCPAdapter:
|
|
292
|
+
"""
|
|
293
|
+
Create a Traia MCP adapter with authentication.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
url: MCP server URL
|
|
297
|
+
api_key: API key for authentication
|
|
298
|
+
auth_header: Header name for auth (default: Authorization)
|
|
299
|
+
auth_prefix: Auth scheme prefix (default: Bearer)
|
|
300
|
+
transport: Transport type (default: streamable-http)
|
|
301
|
+
tool_names: Optional list of tool names to filter
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
TraiaMCPAdapter configured with auth headers
|
|
305
|
+
|
|
306
|
+
Example:
|
|
307
|
+
```python
|
|
308
|
+
adapter = create_mcp_adapter_with_auth(
|
|
309
|
+
url="https://news-mcp.example.com/mcp",
|
|
310
|
+
api_key="your-api-key"
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
with adapter as tools:
|
|
314
|
+
# Tools will include Authorization header in all requests
|
|
315
|
+
agent = Agent(tools=tools)
|
|
316
|
+
```
|
|
317
|
+
"""
|
|
318
|
+
headers = {}
|
|
319
|
+
if auth_prefix:
|
|
320
|
+
headers[auth_header] = f"{auth_prefix} {api_key}"
|
|
321
|
+
else:
|
|
322
|
+
headers[auth_header] = api_key
|
|
323
|
+
|
|
324
|
+
return create_mcp_adapter(url, headers, transport, tool_names)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def create_mcp_adapter_with_x402(
|
|
328
|
+
url: str,
|
|
329
|
+
account: Any,
|
|
330
|
+
max_value: Optional[int] = None,
|
|
331
|
+
transport: str = "streamable-http",
|
|
332
|
+
tool_names: Optional[List[str]] = None
|
|
333
|
+
) -> TraiaMCPAdapter:
|
|
334
|
+
"""
|
|
335
|
+
Create a Traia MCP adapter with d402 payment protocol support.
|
|
336
|
+
|
|
337
|
+
Note: Function name uses "x402" for backward compatibility, but it uses
|
|
338
|
+
the d402 implementation from traia_iatp.d402.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
url: MCP server URL
|
|
342
|
+
account: eth_account.Account instance for signing payments
|
|
343
|
+
max_value: Optional maximum allowed payment amount in base units.
|
|
344
|
+
Typically, each MCP server uses one primary token, so this limit
|
|
345
|
+
applies to all endpoints using that token. Set based on the token's
|
|
346
|
+
base units (e.g., USDC with 6 decimals: $1.00 = 1_000_000).
|
|
347
|
+
transport: Transport type (default: streamable-http)
|
|
348
|
+
tool_names: Optional list of tool names to filter
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
TraiaMCPAdapter configured with d402 payment hooks
|
|
352
|
+
|
|
353
|
+
Example:
|
|
354
|
+
```python
|
|
355
|
+
from eth_account import Account
|
|
356
|
+
|
|
357
|
+
account = Account.from_key("0x...")
|
|
358
|
+
adapter = create_mcp_adapter_with_x402(
|
|
359
|
+
url="https://news-mcp.example.com/mcp",
|
|
360
|
+
account=account,
|
|
361
|
+
max_value=1_000_000 # $1.00 in USDC (6 decimals) - adjust for your server's token
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
with adapter as tools:
|
|
365
|
+
# Tools will automatically handle 402 Payment Required responses
|
|
366
|
+
agent = Agent(tools=tools)
|
|
367
|
+
```
|
|
368
|
+
"""
|
|
369
|
+
if not D402_AVAILABLE:
|
|
370
|
+
raise ImportError(
|
|
371
|
+
"d402 payment hooks not available. Ensure traia_iatp.d402 is installed."
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
return create_mcp_adapter(
|
|
375
|
+
url=url,
|
|
376
|
+
transport=transport,
|
|
377
|
+
tool_names=tool_names,
|
|
378
|
+
x402_account=account, # Function param uses x402 for backward compat
|
|
379
|
+
x402_max_value=max_value # Will be converted to d402 internally
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
# Backwards compatibility aliases
|
|
384
|
+
HeaderMCPAdapter = TraiaMCPAdapter
|
|
385
|
+
create_mcp_adapter_with_headers = create_mcp_adapter
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
# Usage examples
|
|
389
|
+
USAGE_GUIDE = """
|
|
390
|
+
TraiaMCPAdapter Usage Guide
|
|
391
|
+
==========================
|
|
392
|
+
|
|
393
|
+
The TraiaMCPAdapter seamlessly handles both authenticated and non-authenticated
|
|
394
|
+
MCP connections. It extracts headers from server_params and injects them into
|
|
395
|
+
all HTTP requests when using streamable-http transport.
|
|
396
|
+
|
|
397
|
+
Basic Usage
|
|
398
|
+
-----------
|
|
399
|
+
```python
|
|
400
|
+
from traia_iatp.mcp import TraiaMCPAdapter
|
|
401
|
+
|
|
402
|
+
# Standard connection (no authentication)
|
|
403
|
+
server_params = {
|
|
404
|
+
"url": "http://localhost:8000/mcp",
|
|
405
|
+
"transport": "streamable-http"
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
# Authenticated connection
|
|
409
|
+
server_params = {
|
|
410
|
+
"url": "http://localhost:8000/mcp",
|
|
411
|
+
"transport": "streamable-http",
|
|
412
|
+
"headers": {
|
|
413
|
+
"Authorization": "Bearer YOUR_API_KEY"
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
# Use the same way regardless of authentication
|
|
418
|
+
with TraiaMCPAdapter(server_params) as tools:
|
|
419
|
+
agent = Agent(
|
|
420
|
+
role="My Agent",
|
|
421
|
+
tools=tools
|
|
422
|
+
)
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Using Helper Functions
|
|
426
|
+
----------------------
|
|
427
|
+
```python
|
|
428
|
+
from traia_iatp.mcp import create_mcp_adapter_with_auth
|
|
429
|
+
|
|
430
|
+
# Create authenticated adapter
|
|
431
|
+
adapter = create_mcp_adapter_with_auth(
|
|
432
|
+
url="http://localhost:8000/mcp",
|
|
433
|
+
api_key="your-api-key"
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
with adapter as tools:
|
|
437
|
+
# Use authenticated tools
|
|
438
|
+
pass
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Multiple Headers
|
|
442
|
+
----------------
|
|
443
|
+
```python
|
|
444
|
+
adapter = create_mcp_adapter(
|
|
445
|
+
url="http://localhost:8000/mcp",
|
|
446
|
+
headers={
|
|
447
|
+
"Authorization": "Bearer YOUR_API_KEY",
|
|
448
|
+
"X-API-Version": "v1",
|
|
449
|
+
"X-Client-ID": "my-client"
|
|
450
|
+
}
|
|
451
|
+
)
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
With Tool Filtering
|
|
455
|
+
-------------------
|
|
456
|
+
```python
|
|
457
|
+
adapter = create_mcp_adapter_with_auth(
|
|
458
|
+
url="http://localhost:8000/mcp",
|
|
459
|
+
api_key="your-api-key",
|
|
460
|
+
tool_names=["search_news", "get_api_info"]
|
|
461
|
+
)
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Server-Side Authentication
|
|
465
|
+
--------------------------
|
|
466
|
+
For MCP servers that require authentication, implement FastMCP middleware:
|
|
467
|
+
|
|
468
|
+
```python
|
|
469
|
+
from fastmcp import FastMCP, Context
|
|
470
|
+
from fastmcp.server.middleware import Middleware, MiddlewareContext
|
|
471
|
+
from fastmcp.server.dependencies import get_http_request, get_context
|
|
472
|
+
from starlette.requests import Request
|
|
473
|
+
|
|
474
|
+
class AuthMiddleware(Middleware):
|
|
475
|
+
async def on_request(self, context: MiddlewareContext, call_next):
|
|
476
|
+
try:
|
|
477
|
+
# Access the raw HTTP request
|
|
478
|
+
request: Request = get_http_request()
|
|
479
|
+
|
|
480
|
+
# Extract bearer token from Authorization header
|
|
481
|
+
auth = request.headers.get("Authorization", "")
|
|
482
|
+
token = auth[7:].strip() if auth.lower().startswith("bearer ") else None
|
|
483
|
+
|
|
484
|
+
if not token:
|
|
485
|
+
# Check X-API-KEY header as alternative
|
|
486
|
+
token = request.headers.get("X-API-KEY", "")
|
|
487
|
+
|
|
488
|
+
if token:
|
|
489
|
+
# Store the API key in the context state
|
|
490
|
+
if hasattr(context, 'state'):
|
|
491
|
+
context.state.api_key = token
|
|
492
|
+
else:
|
|
493
|
+
# Try to store it in the request state as fallback
|
|
494
|
+
request.state.api_key = token
|
|
495
|
+
except Exception as e:
|
|
496
|
+
logger.debug(f"Could not extract API key from request: {e}")
|
|
497
|
+
|
|
498
|
+
return await call_next(context)
|
|
499
|
+
|
|
500
|
+
mcp = FastMCP("My Server", middleware=[AuthMiddleware()])
|
|
501
|
+
|
|
502
|
+
def get_session_api_key(context: Context) -> Optional[str]:
|
|
503
|
+
'''Get the API key for the current session.'''
|
|
504
|
+
try:
|
|
505
|
+
# Try to get the API key from the context state
|
|
506
|
+
if hasattr(context, 'state') and hasattr(context.state, 'api_key'):
|
|
507
|
+
return context.state.api_key
|
|
508
|
+
|
|
509
|
+
# Fallback: try to get it from the current HTTP request
|
|
510
|
+
try:
|
|
511
|
+
request: Request = get_http_request()
|
|
512
|
+
if hasattr(request.state, 'api_key'):
|
|
513
|
+
return request.state.api_key
|
|
514
|
+
except Exception:
|
|
515
|
+
pass
|
|
516
|
+
|
|
517
|
+
# If we're in a tool context, try to get the context using the dependency
|
|
518
|
+
try:
|
|
519
|
+
ctx = get_context()
|
|
520
|
+
if hasattr(ctx, 'state') and hasattr(ctx.state, 'api_key'):
|
|
521
|
+
return ctx.state.api_key
|
|
522
|
+
except Exception:
|
|
523
|
+
pass
|
|
524
|
+
except Exception as e:
|
|
525
|
+
logger.debug(f"Could not retrieve API key from context: {e}")
|
|
526
|
+
|
|
527
|
+
return None
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Note: Headers are only applicable for streamable-http transport.
|
|
531
|
+
For stdio or SSE transports, authentication must be handled differently.
|
|
532
|
+
"""
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
__all__ = [
|
|
536
|
+
'TraiaMCPAdapter',
|
|
537
|
+
'HeaderMCPAdapter', # Alias for backward compatibility
|
|
538
|
+
'create_mcp_adapter',
|
|
539
|
+
'create_mcp_adapter_with_auth',
|
|
540
|
+
'create_mcp_adapter_with_x402',
|
|
541
|
+
'create_mcp_adapter_with_headers', # Alias
|
|
542
|
+
'USAGE_GUIDE'
|
|
543
|
+
]
|