ctxprotocol 0.5.6__tar.gz → 0.6.0__tar.gz
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.
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/.gitignore +1 -0
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/PKG-INFO +32 -2
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/README.md +31 -1
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/ctxprotocol/__init__.py +45 -0
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/ctxprotocol/auth/__init__.py +111 -12
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/ctxprotocol/client/resources/discovery.py +2 -1
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/ctxprotocol/client/resources/tools.py +1 -1
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/ctxprotocol/client/types.py +31 -8
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/ctxprotocol/context/__init__.py +22 -8
- ctxprotocol-0.6.0/ctxprotocol/handshake/__init__.py +427 -0
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/examples/server/hummingbot-contributor/README.md +65 -23
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/examples/server/hummingbot-contributor/env.example +1 -0
- ctxprotocol-0.6.0/examples/server/hummingbot-contributor/requirements.txt +23 -0
- ctxprotocol-0.6.0/examples/server/hummingbot-contributor/server.py +769 -0
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/pyproject.toml +1 -1
- ctxprotocol-0.5.6/examples/server/hummingbot-contributor/requirements.txt +0 -20
- ctxprotocol-0.5.6/examples/server/hummingbot-contributor/server.py +0 -784
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/ctxprotocol/client/__init__.py +0 -0
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/ctxprotocol/client/client.py +0 -0
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/ctxprotocol/client/resources/__init__.py +0 -0
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/ctxprotocol/context/hyperliquid.py +0 -0
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/ctxprotocol/context/polymarket.py +0 -0
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/ctxprotocol/context/wallet.py +0 -0
- {ctxprotocol-0.5.6 → ctxprotocol-0.6.0}/ctxprotocol/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ctxprotocol
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Official Python SDK for the Context Protocol - Discover and execute AI tools programmatically
|
|
5
5
|
Project-URL: Homepage, https://ctxprotocol.com
|
|
6
6
|
Project-URL: Documentation, https://docs.ctxprotocol.com
|
|
@@ -48,12 +48,26 @@ Context Protocol is **pip for AI capabilities**. Just as you install packages to
|
|
|
48
48
|
[](https://pypi.org/project/ctxprotocol/)
|
|
49
49
|
[](https://opensource.org/licenses/MIT)
|
|
50
50
|
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
### 💰 $10,000 Developer Grant Program
|
|
54
|
+
|
|
55
|
+
We're funding the initial supply of MCP Tools for the Context Marketplace. **Become a Data Broker.**
|
|
56
|
+
|
|
57
|
+
- **🛠️ Build:** Create an MCP Server using this SDK (Solana data, Trading tools, Scrapers, etc.)
|
|
58
|
+
- **📦 List:** Publish it to the Context Registry
|
|
59
|
+
- **💵 Earn:** Get a **$250–$1,000 Grant** + earn USDC every time an agent queries your tool
|
|
60
|
+
|
|
61
|
+
👉 [**View Open Bounties & Apply Here**](https://docs.ctxprotocol.com/grants)
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
51
65
|
## Why use Context?
|
|
52
66
|
|
|
53
67
|
- **🔌 One Interface, Everything:** Stop integrating APIs one by one. Use a single SDK to access any tool in the marketplace.
|
|
54
68
|
- **🧠 Zero-Ops:** We're a gateway to the best MCP tools. Just send the JSON and get the result.
|
|
55
69
|
- **⚡️ Agentic Discovery:** Your Agent can search the marketplace at runtime to find tools it didn't know it needed.
|
|
56
|
-
- **💸
|
|
70
|
+
- **💸 Pay-Per-Response:** The $500/year subscription? Now $0.01/response. No monthly fees, just results.
|
|
57
71
|
|
|
58
72
|
## Who Is This SDK For?
|
|
59
73
|
|
|
@@ -282,6 +296,22 @@ The SDK implements a **selective authentication** model — discovery is open, e
|
|
|
282
296
|
|
|
283
297
|
This matches standard API patterns (OpenAPI schemas are public, GraphQL introspection is open).
|
|
284
298
|
|
|
299
|
+
## Execution Timeout & Product Design
|
|
300
|
+
|
|
301
|
+
⚠️ **Important**: MCP tool execution has a **~60 second timeout** (enforced at the platform/client level, not by MCP itself). This is intentional—it encourages building pre-computed insight products rather than raw data access.
|
|
302
|
+
|
|
303
|
+
**Best practice**: Run heavy queries offline (via cron jobs), store results in your database, and serve instant results via MCP. This is how Bloomberg, Nansen, and Arkham work.
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
# ❌ BAD: Raw access (timeout-prone, no moat)
|
|
307
|
+
{"name": "run_sql", "description": "Run any SQL against blockchain data"}
|
|
308
|
+
|
|
309
|
+
# ✅ GOOD: Pre-computed product (instant, defensible)
|
|
310
|
+
{"name": "get_smart_money_wallets", "description": "Top 100 wallets that timed market tops"}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
See the [full documentation](https://docs.ctxprotocol.com/guides/build-tools#execution-limits--product-design) for detailed guidance.
|
|
314
|
+
|
|
285
315
|
## Context Injection (Personalized Tools)
|
|
286
316
|
|
|
287
317
|
For tools that analyze user data, Context automatically injects user context:
|
|
@@ -10,12 +10,26 @@ Context Protocol is **pip for AI capabilities**. Just as you install packages to
|
|
|
10
10
|
[](https://pypi.org/project/ctxprotocol/)
|
|
11
11
|
[](https://opensource.org/licenses/MIT)
|
|
12
12
|
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
### 💰 $10,000 Developer Grant Program
|
|
16
|
+
|
|
17
|
+
We're funding the initial supply of MCP Tools for the Context Marketplace. **Become a Data Broker.**
|
|
18
|
+
|
|
19
|
+
- **🛠️ Build:** Create an MCP Server using this SDK (Solana data, Trading tools, Scrapers, etc.)
|
|
20
|
+
- **📦 List:** Publish it to the Context Registry
|
|
21
|
+
- **💵 Earn:** Get a **$250–$1,000 Grant** + earn USDC every time an agent queries your tool
|
|
22
|
+
|
|
23
|
+
👉 [**View Open Bounties & Apply Here**](https://docs.ctxprotocol.com/grants)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
13
27
|
## Why use Context?
|
|
14
28
|
|
|
15
29
|
- **🔌 One Interface, Everything:** Stop integrating APIs one by one. Use a single SDK to access any tool in the marketplace.
|
|
16
30
|
- **🧠 Zero-Ops:** We're a gateway to the best MCP tools. Just send the JSON and get the result.
|
|
17
31
|
- **⚡️ Agentic Discovery:** Your Agent can search the marketplace at runtime to find tools it didn't know it needed.
|
|
18
|
-
- **💸
|
|
32
|
+
- **💸 Pay-Per-Response:** The $500/year subscription? Now $0.01/response. No monthly fees, just results.
|
|
19
33
|
|
|
20
34
|
## Who Is This SDK For?
|
|
21
35
|
|
|
@@ -244,6 +258,22 @@ The SDK implements a **selective authentication** model — discovery is open, e
|
|
|
244
258
|
|
|
245
259
|
This matches standard API patterns (OpenAPI schemas are public, GraphQL introspection is open).
|
|
246
260
|
|
|
261
|
+
## Execution Timeout & Product Design
|
|
262
|
+
|
|
263
|
+
⚠️ **Important**: MCP tool execution has a **~60 second timeout** (enforced at the platform/client level, not by MCP itself). This is intentional—it encourages building pre-computed insight products rather than raw data access.
|
|
264
|
+
|
|
265
|
+
**Best practice**: Run heavy queries offline (via cron jobs), store results in your database, and serve instant results via MCP. This is how Bloomberg, Nansen, and Arkham work.
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
# ❌ BAD: Raw access (timeout-prone, no moat)
|
|
269
|
+
{"name": "run_sql", "description": "Run any SQL against blockchain data"}
|
|
270
|
+
|
|
271
|
+
# ✅ GOOD: Pre-computed product (instant, defensible)
|
|
272
|
+
{"name": "get_smart_money_wallets", "description": "Top 100 wallets that timed market tops"}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
See the [full documentation](https://docs.ctxprotocol.com/guides/build-tools#execution-limits--product-design) for detailed guidance.
|
|
276
|
+
|
|
247
277
|
## Context Injection (Personalized Tools)
|
|
248
278
|
|
|
249
279
|
For tools that analyze user data, Context automatically injects user context:
|
|
@@ -91,6 +91,31 @@ from ctxprotocol.auth import (
|
|
|
91
91
|
CreateContextMiddlewareOptions,
|
|
92
92
|
)
|
|
93
93
|
|
|
94
|
+
# Handshake types and helpers for tools that need user interaction
|
|
95
|
+
# (signatures, transactions, OAuth)
|
|
96
|
+
from ctxprotocol.handshake import (
|
|
97
|
+
# Types
|
|
98
|
+
HandshakeMeta,
|
|
99
|
+
EIP712Domain,
|
|
100
|
+
EIP712TypeField,
|
|
101
|
+
SignatureRequest,
|
|
102
|
+
TransactionProposalMeta,
|
|
103
|
+
TransactionProposal,
|
|
104
|
+
AuthRequiredMeta,
|
|
105
|
+
AuthRequired,
|
|
106
|
+
HandshakeAction,
|
|
107
|
+
# Type guards
|
|
108
|
+
is_handshake_action,
|
|
109
|
+
is_signature_request,
|
|
110
|
+
is_transaction_proposal,
|
|
111
|
+
is_auth_required,
|
|
112
|
+
# Helper functions
|
|
113
|
+
create_signature_request,
|
|
114
|
+
create_transaction_proposal,
|
|
115
|
+
create_auth_required,
|
|
116
|
+
wrap_handshake_response,
|
|
117
|
+
)
|
|
118
|
+
|
|
94
119
|
__all__ = [
|
|
95
120
|
# Version
|
|
96
121
|
"__version__",
|
|
@@ -144,5 +169,25 @@ __all__ = [
|
|
|
144
169
|
"ContextMiddleware",
|
|
145
170
|
"VerifyRequestOptions",
|
|
146
171
|
"CreateContextMiddlewareOptions",
|
|
172
|
+
# Handshake types
|
|
173
|
+
"HandshakeMeta",
|
|
174
|
+
"EIP712Domain",
|
|
175
|
+
"EIP712TypeField",
|
|
176
|
+
"SignatureRequest",
|
|
177
|
+
"TransactionProposalMeta",
|
|
178
|
+
"TransactionProposal",
|
|
179
|
+
"AuthRequiredMeta",
|
|
180
|
+
"AuthRequired",
|
|
181
|
+
"HandshakeAction",
|
|
182
|
+
# Handshake type guards
|
|
183
|
+
"is_handshake_action",
|
|
184
|
+
"is_signature_request",
|
|
185
|
+
"is_transaction_proposal",
|
|
186
|
+
"is_auth_required",
|
|
187
|
+
# Handshake helper functions
|
|
188
|
+
"create_signature_request",
|
|
189
|
+
"create_transaction_proposal",
|
|
190
|
+
"create_auth_required",
|
|
191
|
+
"wrap_handshake_response",
|
|
147
192
|
]
|
|
148
193
|
|
|
@@ -38,6 +38,54 @@ weipe6amt9lzQzi8WXaFKpOXHQs//WDlUytz/Hl8pvd5craZKzo6Kyrg1Vfan7H3
|
|
|
38
38
|
TQIDAQAB
|
|
39
39
|
-----END PUBLIC KEY-----"""
|
|
40
40
|
|
|
41
|
+
# ============================================================================
|
|
42
|
+
# JWKS Key Fetching (with hardcoded fallback)
|
|
43
|
+
# ============================================================================
|
|
44
|
+
|
|
45
|
+
JWKS_URL = "https://ctxprotocol.com/.well-known/jwks.json"
|
|
46
|
+
_KEY_CACHE_TTL_SECONDS = 3600 # 1 hour
|
|
47
|
+
|
|
48
|
+
_cached_public_key: Any = None
|
|
49
|
+
_cache_timestamp: float = 0
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def _get_platform_public_key() -> Any:
|
|
53
|
+
"""Get the platform public key, trying JWKS endpoint first with hardcoded fallback.
|
|
54
|
+
|
|
55
|
+
Caches the result for 1 hour.
|
|
56
|
+
"""
|
|
57
|
+
import time
|
|
58
|
+
global _cached_public_key, _cache_timestamp
|
|
59
|
+
|
|
60
|
+
now = time.time()
|
|
61
|
+
|
|
62
|
+
# Return cached key if still valid
|
|
63
|
+
if _cached_public_key is not None and now - _cache_timestamp < _KEY_CACHE_TTL_SECONDS:
|
|
64
|
+
return _cached_public_key
|
|
65
|
+
|
|
66
|
+
# Try JWKS endpoint first
|
|
67
|
+
try:
|
|
68
|
+
import httpx
|
|
69
|
+
async with httpx.AsyncClient(timeout=5.0) as client:
|
|
70
|
+
response = await client.get(JWKS_URL)
|
|
71
|
+
if response.is_success:
|
|
72
|
+
jwks = response.json()
|
|
73
|
+
if jwks.get("keys") and len(jwks["keys"]) > 0:
|
|
74
|
+
# For now, use hardcoded key (JWKS parsing requires additional logic)
|
|
75
|
+
# This establishes the pattern for when the endpoint is deployed
|
|
76
|
+
pass
|
|
77
|
+
except Exception:
|
|
78
|
+
# JWKS fetch failed - fall back to hardcoded key
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
# Fallback: use hardcoded key
|
|
82
|
+
_cached_public_key = serialization.load_pem_public_key(
|
|
83
|
+
CONTEXT_PLATFORM_PUBLIC_KEY_PEM.encode()
|
|
84
|
+
)
|
|
85
|
+
_cache_timestamp = now
|
|
86
|
+
return _cached_public_key
|
|
87
|
+
|
|
88
|
+
|
|
41
89
|
# MCP methods that require authentication
|
|
42
90
|
# - tools/call: Executes tool logic, may cost money
|
|
43
91
|
# - resources/read: Reads potentially sensitive data
|
|
@@ -155,10 +203,8 @@ async def verify_context_request(
|
|
|
155
203
|
token = authorization_header.split(" ", 1)[1]
|
|
156
204
|
|
|
157
205
|
try:
|
|
158
|
-
# Load the public key
|
|
159
|
-
public_key =
|
|
160
|
-
CONTEXT_PLATFORM_PUBLIC_KEY_PEM.encode()
|
|
161
|
-
)
|
|
206
|
+
# Load the public key (tries JWKS endpoint first, falls back to hardcoded)
|
|
207
|
+
public_key = await _get_platform_public_key()
|
|
162
208
|
|
|
163
209
|
# Build decode options - match TypeScript SDK behavior
|
|
164
210
|
decode_options: dict[str, Any] = {
|
|
@@ -300,22 +346,74 @@ class ContextMiddleware:
|
|
|
300
346
|
await self.app(scope, receive, send)
|
|
301
347
|
return
|
|
302
348
|
|
|
303
|
-
#
|
|
304
|
-
# This is a simplified version - in production you might want to
|
|
305
|
-
# use a more sophisticated approach to avoid reading the body twice
|
|
349
|
+
# Buffer the request body to inspect the MCP method
|
|
306
350
|
body_parts: list[bytes] = []
|
|
351
|
+
body_complete = False
|
|
307
352
|
|
|
308
353
|
async def receive_wrapper() -> dict[str, Any]:
|
|
354
|
+
nonlocal body_complete
|
|
355
|
+
if body_parts and body_complete:
|
|
356
|
+
# Replay the buffered body
|
|
357
|
+
body = b"".join(body_parts)
|
|
358
|
+
return {"type": "http.request", "body": body, "more_body": False}
|
|
359
|
+
|
|
309
360
|
message = await receive()
|
|
310
361
|
if message["type"] == "http.request":
|
|
311
362
|
body_parts.append(message.get("body", b""))
|
|
363
|
+
if not message.get("more_body", False):
|
|
364
|
+
body_complete = True
|
|
312
365
|
return message
|
|
313
366
|
|
|
314
|
-
#
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
367
|
+
# Read the body to check the method
|
|
368
|
+
while not body_complete:
|
|
369
|
+
await receive_wrapper()
|
|
370
|
+
|
|
371
|
+
# Parse the body to get the MCP method
|
|
372
|
+
try:
|
|
373
|
+
import json
|
|
374
|
+
body_bytes = b"".join(body_parts)
|
|
375
|
+
body_json = json.loads(body_bytes)
|
|
376
|
+
method = body_json.get("method", "")
|
|
377
|
+
except Exception:
|
|
378
|
+
# Can't parse body - let the endpoint handle it
|
|
379
|
+
await self.app(scope, receive_wrapper, send)
|
|
380
|
+
return
|
|
381
|
+
|
|
382
|
+
# Allow discovery methods without authentication
|
|
383
|
+
if not method or not is_protected_mcp_method(method):
|
|
384
|
+
await self.app(scope, receive_wrapper, send)
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
# Protected method - require authentication
|
|
388
|
+
# Extract Authorization header from ASGI scope
|
|
389
|
+
headers = dict(scope.get("headers", []))
|
|
390
|
+
auth_header = headers.get(b"authorization", b"").decode("utf-8")
|
|
391
|
+
|
|
392
|
+
try:
|
|
393
|
+
payload = await verify_context_request(
|
|
394
|
+
authorization_header=auth_header if auth_header else None,
|
|
395
|
+
audience=self.audience,
|
|
396
|
+
)
|
|
397
|
+
# Attach payload to scope state for downstream use
|
|
398
|
+
if "state" not in scope:
|
|
399
|
+
scope["state"] = {}
|
|
400
|
+
scope["state"]["context"] = payload
|
|
401
|
+
await self.app(scope, receive_wrapper, send)
|
|
402
|
+
except ContextError as e:
|
|
403
|
+
# Return 401 JSON response
|
|
404
|
+
import json
|
|
405
|
+
response_body = json.dumps({"error": str(e)}).encode("utf-8")
|
|
406
|
+
await send({
|
|
407
|
+
"type": "http.response.start",
|
|
408
|
+
"status": e.status_code or 401,
|
|
409
|
+
"headers": [
|
|
410
|
+
[b"content-type", b"application/json"],
|
|
411
|
+
],
|
|
412
|
+
})
|
|
413
|
+
await send({
|
|
414
|
+
"type": "http.response.body",
|
|
415
|
+
"body": response_body,
|
|
416
|
+
})
|
|
319
417
|
|
|
320
418
|
|
|
321
419
|
def create_context_middleware(
|
|
@@ -380,6 +478,7 @@ def create_context_middleware(
|
|
|
380
478
|
__all__ = [
|
|
381
479
|
# Constants
|
|
382
480
|
"CONTEXT_PLATFORM_PUBLIC_KEY_PEM",
|
|
481
|
+
"JWKS_URL",
|
|
383
482
|
"PROTECTED_MCP_METHODS",
|
|
384
483
|
"OPEN_MCP_METHODS",
|
|
385
484
|
# Method classification
|
|
@@ -46,7 +46,8 @@ class Discovery:
|
|
|
46
46
|
if limit is not None:
|
|
47
47
|
params["limit"] = str(limit)
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
from urllib.parse import urlencode
|
|
50
|
+
query_string = urlencode(params) if params else ""
|
|
50
51
|
endpoint = f"/api/v1/tools/search{'?' + query_string if query_string else ''}"
|
|
51
52
|
|
|
52
53
|
response = await self._client.fetch(endpoint)
|
|
@@ -60,9 +60,13 @@ class Tool(BaseModel):
|
|
|
60
60
|
price: Price per execution in USDC
|
|
61
61
|
category: Tool category (e.g., "defi", "nft")
|
|
62
62
|
is_verified: Whether the tool is verified by Context Protocol
|
|
63
|
+
kind: Tool type - currently always "mcp"
|
|
63
64
|
mcp_tools: Available MCP tool methods
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
total_queries: Total number of queries processed
|
|
66
|
+
success_rate: Success rate percentage (0-100)
|
|
67
|
+
uptime_percent: Uptime percentage (0-100)
|
|
68
|
+
total_staked: Total USDC staked by the developer
|
|
69
|
+
is_proven: Whether the tool has "Proven" status
|
|
66
70
|
"""
|
|
67
71
|
|
|
68
72
|
id: str = Field(..., description="Unique identifier for the tool (UUID)")
|
|
@@ -75,20 +79,39 @@ class Tool(BaseModel):
|
|
|
75
79
|
alias="isVerified",
|
|
76
80
|
description="Whether the tool is verified by Context Protocol",
|
|
77
81
|
)
|
|
82
|
+
kind: str | None = Field(
|
|
83
|
+
default=None,
|
|
84
|
+
description="Tool type - currently always 'mcp'",
|
|
85
|
+
)
|
|
78
86
|
mcp_tools: list[McpTool] | None = Field(
|
|
79
87
|
default=None,
|
|
80
88
|
alias="mcpTools",
|
|
81
89
|
description="Available MCP tool methods",
|
|
82
90
|
)
|
|
83
|
-
|
|
91
|
+
total_queries: int | None = Field(
|
|
92
|
+
default=None,
|
|
93
|
+
alias="totalQueries",
|
|
94
|
+
description="Total number of queries processed",
|
|
95
|
+
)
|
|
96
|
+
success_rate: str | None = Field(
|
|
97
|
+
default=None,
|
|
98
|
+
alias="successRate",
|
|
99
|
+
description="Success rate percentage",
|
|
100
|
+
)
|
|
101
|
+
uptime_percent: str | None = Field(
|
|
102
|
+
default=None,
|
|
103
|
+
alias="uptimePercent",
|
|
104
|
+
description="Uptime percentage",
|
|
105
|
+
)
|
|
106
|
+
total_staked: str | None = Field(
|
|
84
107
|
default=None,
|
|
85
|
-
alias="
|
|
86
|
-
description="
|
|
108
|
+
alias="totalStaked",
|
|
109
|
+
description="Total USDC staked by developer",
|
|
87
110
|
)
|
|
88
|
-
|
|
111
|
+
is_proven: bool | None = Field(
|
|
89
112
|
default=None,
|
|
90
|
-
alias="
|
|
91
|
-
description="
|
|
113
|
+
alias="isProven",
|
|
114
|
+
description="Whether tool has Proven status",
|
|
92
115
|
)
|
|
93
116
|
|
|
94
117
|
model_config = {"populate_by_name": True}
|
|
@@ -69,24 +69,37 @@ from ctxprotocol.context.hyperliquid import (
|
|
|
69
69
|
|
|
70
70
|
CONTEXT_REQUIREMENTS_KEY = "x-context-requirements"
|
|
71
71
|
"""
|
|
72
|
-
|
|
72
|
+
DEPRECATED: Use _meta.contextRequirements instead.
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- Custom fields like `requirements` get stripped by MCP SDK during transport
|
|
77
|
-
- JSON Schema allows custom "x-" prefixed extension properties
|
|
78
|
-
- inputSchema is preserved end-to-end through MCP transport
|
|
74
|
+
The primary mechanism for declaring context requirements is via the MCP _meta field
|
|
75
|
+
at the tool level, which is preserved by the MCP SDK through transport:
|
|
79
76
|
|
|
80
|
-
Example:
|
|
81
77
|
>>> tool = {
|
|
82
78
|
... "name": "analyze_my_positions",
|
|
79
|
+
... "_meta": {"contextRequirements": ["hyperliquid"]},
|
|
83
80
|
... "inputSchema": {
|
|
84
81
|
... "type": "object",
|
|
85
|
-
... CONTEXT_REQUIREMENTS_KEY: ["hyperliquid"],
|
|
86
82
|
... "properties": {"portfolio": {"type": "object"}},
|
|
87
83
|
... "required": ["portfolio"]
|
|
88
84
|
... }
|
|
89
85
|
... }
|
|
86
|
+
|
|
87
|
+
This constant is kept for backwards compatibility but _meta.contextRequirements
|
|
88
|
+
is what the Context Platform reads. The x-context-requirements inputSchema extension
|
|
89
|
+
may be stripped by some MCP transports.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
META_CONTEXT_REQUIREMENTS_KEY = "contextRequirements"
|
|
93
|
+
"""
|
|
94
|
+
The key used inside _meta to declare context requirements.
|
|
95
|
+
This is the primary mechanism - the Context Platform reads _meta.contextRequirements.
|
|
96
|
+
|
|
97
|
+
Example:
|
|
98
|
+
>>> tool = {
|
|
99
|
+
... "name": "analyze_positions",
|
|
100
|
+
... "_meta": {META_CONTEXT_REQUIREMENTS_KEY: ["hyperliquid"]},
|
|
101
|
+
... "inputSchema": {...}
|
|
102
|
+
... }
|
|
90
103
|
"""
|
|
91
104
|
|
|
92
105
|
# Context requirement types supported by the Context marketplace
|
|
@@ -158,6 +171,7 @@ class UserContext(BaseModel):
|
|
|
158
171
|
__all__ = [
|
|
159
172
|
# Constants
|
|
160
173
|
"CONTEXT_REQUIREMENTS_KEY",
|
|
174
|
+
"META_CONTEXT_REQUIREMENTS_KEY",
|
|
161
175
|
# Type aliases
|
|
162
176
|
"ContextRequirementType",
|
|
163
177
|
# Wallet types
|