@ctxprotocol/sdk 0.5.5 → 0.5.6
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/README.md +123 -47
- package/dist/index.cjs +58 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +238 -1
- package/dist/index.d.ts +238 -1
- package/dist/index.js +51 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,23 +9,41 @@ Context Protocol is **npm for AI capabilities**. Just as you install packages to
|
|
|
9
9
|
[](https://www.npmjs.com/package/@ctxprotocol/sdk)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
11
|
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
### 💰 $10,000 Developer Grant Program
|
|
15
|
+
|
|
16
|
+
We're funding the initial supply of MCP Tools for the Context Marketplace. **Become a Data Broker.**
|
|
17
|
+
|
|
18
|
+
- **🛠️ Build:** Create an MCP Server using this SDK (Solana data, Trading tools, Scrapers, etc.)
|
|
19
|
+
- **📦 List:** Publish it to the Context Registry
|
|
20
|
+
- **💵 Earn:** Get a **$250–$1,000 Grant** + earn USDC every time an agent queries your tool
|
|
21
|
+
|
|
22
|
+
👉 [**View Open Bounties & Apply Here**](https://docs.ctxprotocol.com/grants)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
12
26
|
## Why use Context?
|
|
13
27
|
|
|
14
28
|
- **🔌 One Interface, Everything:** Stop integrating APIs one by one. Use a single SDK to access any tool in the marketplace.
|
|
15
29
|
- **🧠 Zero-Ops:** We're a gateway to the best MCP tools. Just send the JSON and get the result.
|
|
16
30
|
- **⚡️ Agentic Discovery:** Your Agent can search the marketplace at runtime to find tools it didn't know it needed.
|
|
17
|
-
- **💸
|
|
31
|
+
- **💸 Pay-Per-Response:** The $500/year subscription? Now $0.01/response. No monthly fees, just results.
|
|
18
32
|
|
|
19
33
|
## Who Is This SDK For?
|
|
20
34
|
|
|
21
|
-
**This SDK is for AI Agent developers** who want to query the Context marketplace and execute tools.
|
|
22
|
-
|
|
23
35
|
| Role | What You Use |
|
|
24
36
|
|------|--------------|
|
|
25
37
|
| **AI Agent Developer** | `@ctxprotocol/sdk` — Query marketplace, execute tools, handle payments |
|
|
26
|
-
| **Tool Contributor (Data Broker)** | `@modelcontextprotocol/sdk` — Standard MCP server +
|
|
38
|
+
| **Tool Contributor (Data Broker)** | `@modelcontextprotocol/sdk` + `@ctxprotocol/sdk` — Standard MCP server + security middleware |
|
|
27
39
|
|
|
28
|
-
|
|
40
|
+
**For AI Agent Developers:** Use this SDK to search the marketplace, execute tools, and handle micro-payments.
|
|
41
|
+
|
|
42
|
+
**For Tool Contributors:** You need **both** SDKs:
|
|
43
|
+
- `@modelcontextprotocol/sdk` — Build your MCP server (tools, schemas, handlers)
|
|
44
|
+
- `@ctxprotocol/sdk` — Secure your endpoint with `createContextMiddleware()` and receive injected user portfolio data
|
|
45
|
+
|
|
46
|
+
See [Building MCP Servers](#building-mcp-servers-tool-contributors) and [Securing Your Tool](#-securing-your-tool) for the complete pattern.
|
|
29
47
|
|
|
30
48
|
## Installation
|
|
31
49
|
|
|
@@ -265,8 +283,6 @@ const result = await client.tools.execute({
|
|
|
265
283
|
|
|
266
284
|
```typescript
|
|
267
285
|
import {
|
|
268
|
-
// Constant for declaring context requirements
|
|
269
|
-
CONTEXT_REQUIREMENTS_KEY,
|
|
270
286
|
// Auth utilities for tool contributors
|
|
271
287
|
verifyContextRequest,
|
|
272
288
|
isProtectedMcpMethod,
|
|
@@ -330,21 +346,27 @@ interface ExecutionResult<T = unknown> {
|
|
|
330
346
|
### Context Requirement Types (MCP Server Contributors)
|
|
331
347
|
|
|
332
348
|
```typescript
|
|
333
|
-
import {
|
|
349
|
+
import type { ContextRequirementType } from "@ctxprotocol/sdk";
|
|
334
350
|
|
|
335
351
|
/** Context types supported by the marketplace */
|
|
336
352
|
type ContextRequirementType = "polymarket" | "hyperliquid" | "wallet";
|
|
337
353
|
|
|
338
|
-
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
354
|
+
// Usage: Add _meta.contextRequirements to your tool definition
|
|
355
|
+
const TOOLS = [{
|
|
356
|
+
name: "analyze_my_positions",
|
|
357
|
+
description: "...",
|
|
358
|
+
|
|
359
|
+
// ⭐ Declare context requirements in _meta (MCP spec)
|
|
360
|
+
_meta: {
|
|
361
|
+
contextRequirements: ["wallet"] as ContextRequirementType[],
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
inputSchema: {
|
|
365
|
+
type: "object",
|
|
366
|
+
properties: { wallet: { type: "object" } },
|
|
367
|
+
required: ["wallet"]
|
|
368
|
+
},
|
|
369
|
+
}];
|
|
348
370
|
```
|
|
349
371
|
|
|
350
372
|
## Error Handling
|
|
@@ -431,17 +453,22 @@ app.post("/mcp", (req, res) => {
|
|
|
431
453
|
| **Free Tools ($0.00)** | Optional | Perfect for distribution and adoption |
|
|
432
454
|
| **Paid Tools ($0.01+)** | **Mandatory** | We cannot route payments to insecure endpoints |
|
|
433
455
|
|
|
434
|
-
### Security Model
|
|
456
|
+
### MCP Security Model
|
|
435
457
|
|
|
436
|
-
The SDK implements a **selective authentication** model:
|
|
458
|
+
The SDK implements a **selective authentication** model — discovery is open, execution is protected:
|
|
437
459
|
|
|
438
|
-
| MCP Method | Auth Required |
|
|
439
|
-
|
|
440
|
-
| `tools/list` | ❌ No | Discovery - just returns tool schemas |
|
|
441
|
-
| `tools/call` | ✅ Yes | Execution - runs code, may cost money |
|
|
460
|
+
| MCP Method | Auth Required | Why |
|
|
461
|
+
|------------|---------------|-----|
|
|
442
462
|
| `initialize` | ❌ No | Session setup |
|
|
463
|
+
| `tools/list` | ❌ No | Discovery - agents need to see your schemas |
|
|
443
464
|
| `resources/list` | ❌ No | Discovery |
|
|
444
465
|
| `prompts/list` | ❌ No | Discovery |
|
|
466
|
+
| `tools/call` | ✅ **Yes** | **Execution - costs money, runs your code** |
|
|
467
|
+
|
|
468
|
+
**What this means in practice:**
|
|
469
|
+
- ✅ `https://your-mcp.com/mcp` + `initialize` → Works without auth
|
|
470
|
+
- ✅ `https://your-mcp.com/mcp` + `tools/list` → Works without auth
|
|
471
|
+
- ❌ `https://your-mcp.com/mcp` + `tools/call` → **Requires Context Protocol JWT**
|
|
445
472
|
|
|
446
473
|
This matches standard API patterns (OpenAPI schemas are public, GraphQL introspection is open).
|
|
447
474
|
|
|
@@ -505,48 +532,86 @@ Want to earn money by contributing tools to the Context marketplace? Build a sta
|
|
|
505
532
|
| `outputSchema` | AI agents use this to generate type-safe code. Context uses it for dispute resolution. |
|
|
506
533
|
| `structuredContent` | Agents parse this for programmatic access. Text `content` is for humans. |
|
|
507
534
|
|
|
508
|
-
### Context Injection (
|
|
535
|
+
### Context Injection (Personalized Tools)
|
|
509
536
|
|
|
510
|
-
Building tools that analyze user
|
|
537
|
+
Building tools that analyze user data? Context automatically injects user context into your tools no authentication required.
|
|
511
538
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
539
|
+
**How it works:**
|
|
540
|
+
1. User connects their wallet in the Context app (we start with blockchain user data, but we're open to other client-side personal data types in the future)
|
|
541
|
+
2. When your tool is selected, the platform reads `_meta.contextRequirements` from your tool definition
|
|
542
|
+
3. Platform fetches the user's data (wallet balances, protocol positions, etc.)
|
|
543
|
+
4. Data is injected as an argument to your tool
|
|
544
|
+
|
|
545
|
+
**Key benefits:**
|
|
546
|
+
- **No Auth Required** — User data is injected automatically from connected wallets
|
|
547
|
+
- **Type-Safe** — Use SDK types like `WalletContext`, `PolymarketContext`, `HyperliquidContext`
|
|
517
548
|
- **Focus on Analysis** — You receive structured data, you provide insights
|
|
518
549
|
|
|
550
|
+
**What gets injected:**
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
// For hyperliquid context requirement
|
|
554
|
+
interface HyperliquidContext {
|
|
555
|
+
walletAddress: string;
|
|
556
|
+
perpPositions: HyperliquidPerpPosition[];
|
|
557
|
+
spotBalances: HyperliquidSpotBalance[];
|
|
558
|
+
openOrders: HyperliquidOrder[];
|
|
559
|
+
accountSummary: HyperliquidAccountSummary;
|
|
560
|
+
fetchedAt: string;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// For polymarket context requirement
|
|
564
|
+
interface PolymarketContext {
|
|
565
|
+
walletAddress: string;
|
|
566
|
+
positions: PolymarketPosition[];
|
|
567
|
+
openOrders: PolymarketOrder[];
|
|
568
|
+
totalValue?: number;
|
|
569
|
+
fetchedAt: string;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// For wallet context requirement
|
|
573
|
+
interface WalletContext {
|
|
574
|
+
address: string;
|
|
575
|
+
chainId: number;
|
|
576
|
+
balances: TokenBalance[];
|
|
577
|
+
fetchedAt: string;
|
|
578
|
+
}
|
|
579
|
+
```
|
|
580
|
+
|
|
519
581
|
### Context Requirements Declaration
|
|
520
582
|
|
|
521
|
-
If your tool needs user portfolio data, you **MUST** declare this using
|
|
583
|
+
If your tool needs user portfolio data, you **MUST** declare this using `_meta.contextRequirements` on the tool definition:
|
|
522
584
|
|
|
523
585
|
```typescript
|
|
524
|
-
import {
|
|
586
|
+
import type { ContextRequirementType } from "@ctxprotocol/sdk";
|
|
525
587
|
|
|
526
588
|
const TOOLS = [{
|
|
527
589
|
name: "analyze_my_positions",
|
|
528
590
|
description: "Analyze your positions with personalized insights",
|
|
529
591
|
|
|
592
|
+
// ⭐ REQUIRED: Context requirements in _meta (MCP spec for arbitrary metadata)
|
|
593
|
+
// The Context platform reads this to inject user data
|
|
594
|
+
_meta: {
|
|
595
|
+
contextRequirements: ["wallet"] as ContextRequirementType[],
|
|
596
|
+
},
|
|
597
|
+
|
|
530
598
|
inputSchema: {
|
|
531
599
|
type: "object",
|
|
532
|
-
// ⭐ REQUIRED: Context requirements embedded in inputSchema
|
|
533
|
-
[CONTEXT_REQUIREMENTS_KEY]: ["hyperliquid"] as ContextRequirementType[],
|
|
534
|
-
// Or use the string directly: "x-context-requirements": ["hyperliquid"]
|
|
535
600
|
properties: {
|
|
536
|
-
|
|
601
|
+
wallet: {
|
|
537
602
|
type: "object",
|
|
538
|
-
description: "
|
|
603
|
+
description: "Wallet context (injected by platform)",
|
|
539
604
|
},
|
|
540
605
|
},
|
|
541
|
-
required: ["
|
|
606
|
+
required: ["wallet"],
|
|
542
607
|
},
|
|
543
608
|
outputSchema: { /* ... */ },
|
|
544
609
|
}];
|
|
545
610
|
```
|
|
546
611
|
|
|
547
|
-
**Why `
|
|
612
|
+
**Why `_meta` at the tool level?**
|
|
548
613
|
|
|
549
|
-
The
|
|
614
|
+
The `_meta` field is part of the [MCP specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-definition) for arbitrary tool metadata. The Context platform reads `_meta.contextRequirements` to determine what user data to inject. This is preserved through MCP transport because it's a standard field.
|
|
550
615
|
|
|
551
616
|
**Available context types:**
|
|
552
617
|
|
|
@@ -556,12 +621,6 @@ The MCP protocol only transmits standard fields (`name`, `description`, `inputSc
|
|
|
556
621
|
| `"polymarket"` | Polymarket prediction markets | `PolymarketContext` |
|
|
557
622
|
| `"wallet"` | Generic EVM wallet | `WalletContext` |
|
|
558
623
|
|
|
559
|
-
**How it works:**
|
|
560
|
-
1. User links their wallet in Context app settings
|
|
561
|
-
2. When your tool is selected, platform reads `inputSchema["x-context-requirements"]`
|
|
562
|
-
3. Platform fetches user's portfolio data from protocol APIs
|
|
563
|
-
4. Data is injected as the `portfolio` argument to your tool
|
|
564
|
-
|
|
565
624
|
### Example: Standard MCP Server
|
|
566
625
|
|
|
567
626
|
Build your server with the standard `@modelcontextprotocol/sdk`:
|
|
@@ -619,6 +678,22 @@ See complete working examples in `/examples/server/`:
|
|
|
619
678
|
- **[blocknative-contributor](./examples/server/blocknative-contributor)** — Gas price API (3 tools)
|
|
620
679
|
- **[hyperliquid-contributor](./examples/server/hyperliquid-contributor)** — DeFi analytics (16 tools)
|
|
621
680
|
|
|
681
|
+
### Execution Timeout & Product Design
|
|
682
|
+
|
|
683
|
+
⚠️ **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.
|
|
684
|
+
|
|
685
|
+
**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—they don't give raw SQL access, they serve curated insights.
|
|
686
|
+
|
|
687
|
+
```typescript
|
|
688
|
+
// ❌ BAD: Raw access (timeout-prone, no moat)
|
|
689
|
+
{ name: "run_sql", description: "Run any SQL against blockchain data" }
|
|
690
|
+
|
|
691
|
+
// ✅ GOOD: Pre-computed product (instant, defensible)
|
|
692
|
+
{ name: "get_smart_money_wallets", description: "Top 100 wallets that timed market tops" }
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
See the [full documentation](https://docs.ctxprotocol.com/guides/build-tools#execution-limits--product-design) for detailed guidance.
|
|
696
|
+
|
|
622
697
|
### Schema Accuracy = Revenue
|
|
623
698
|
|
|
624
699
|
⚠️ **Important**: Your `outputSchema` is a contract. Context's "Robot Judge" validates that your `structuredContent` matches your declared schema. Schema violations result in automatic refunds to users.
|
|
@@ -643,6 +718,7 @@ When you execute a tool:
|
|
|
643
718
|
|
|
644
719
|
| Document | Description |
|
|
645
720
|
|----------|-------------|
|
|
721
|
+
| [MCP Builder Template](./docs/mcp-builder-template.md) | **Start here!** AI-powered template for designing MCP servers with Cursor. Generates discovery questions and tool schemas automatically. |
|
|
646
722
|
| [Context Injection Guide](./docs/context-injection.md) | Architecture guide for building portfolio analysis tools with automatic user data injection |
|
|
647
723
|
| [Polymarket Example](./examples/server/polymarket-contributor) | Complete MCP server for Polymarket intelligence |
|
|
648
724
|
| [Hyperliquid Example](./examples/server/hyperliquid-contributor) | Complete MCP server for Hyperliquid analytics |
|
package/dist/index.cjs
CHANGED
|
@@ -264,14 +264,72 @@ function createContextMiddleware(options = {}) {
|
|
|
264
264
|
};
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
+
// src/handshake/types.ts
|
|
268
|
+
function isHandshakeAction(value) {
|
|
269
|
+
return typeof value === "object" && value !== null && "_action" in value && (value._action === "signature_request" || value._action === "transaction_proposal" || value._action === "auth_required");
|
|
270
|
+
}
|
|
271
|
+
function isSignatureRequest(value) {
|
|
272
|
+
return isHandshakeAction(value) && value._action === "signature_request";
|
|
273
|
+
}
|
|
274
|
+
function isTransactionProposal(value) {
|
|
275
|
+
return isHandshakeAction(value) && value._action === "transaction_proposal";
|
|
276
|
+
}
|
|
277
|
+
function isAuthRequired(value) {
|
|
278
|
+
return isHandshakeAction(value) && value._action === "auth_required";
|
|
279
|
+
}
|
|
280
|
+
function createSignatureRequest(params) {
|
|
281
|
+
return {
|
|
282
|
+
_action: "signature_request",
|
|
283
|
+
...params
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function createTransactionProposal(params) {
|
|
287
|
+
return {
|
|
288
|
+
_action: "transaction_proposal",
|
|
289
|
+
...params
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function createAuthRequired(params) {
|
|
293
|
+
return {
|
|
294
|
+
_action: "auth_required",
|
|
295
|
+
...params
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function wrapHandshakeResponse(action) {
|
|
299
|
+
const actionType = action._action.replace("_", " ");
|
|
300
|
+
return {
|
|
301
|
+
content: [
|
|
302
|
+
{
|
|
303
|
+
type: "text",
|
|
304
|
+
text: `Handshake required: ${actionType}. Please approve in the Context app.`
|
|
305
|
+
}
|
|
306
|
+
],
|
|
307
|
+
structuredContent: {
|
|
308
|
+
_meta: {
|
|
309
|
+
handshakeAction: action
|
|
310
|
+
},
|
|
311
|
+
status: "handshake_required",
|
|
312
|
+
message: action.meta?.description ?? `${actionType} required`
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
267
317
|
exports.CONTEXT_REQUIREMENTS_KEY = CONTEXT_REQUIREMENTS_KEY;
|
|
268
318
|
exports.ContextClient = ContextClient;
|
|
269
319
|
exports.ContextError = ContextError;
|
|
270
320
|
exports.Discovery = Discovery;
|
|
271
321
|
exports.Tools = Tools;
|
|
322
|
+
exports.createAuthRequired = createAuthRequired;
|
|
272
323
|
exports.createContextMiddleware = createContextMiddleware;
|
|
324
|
+
exports.createSignatureRequest = createSignatureRequest;
|
|
325
|
+
exports.createTransactionProposal = createTransactionProposal;
|
|
326
|
+
exports.isAuthRequired = isAuthRequired;
|
|
327
|
+
exports.isHandshakeAction = isHandshakeAction;
|
|
273
328
|
exports.isOpenMcpMethod = isOpenMcpMethod;
|
|
274
329
|
exports.isProtectedMcpMethod = isProtectedMcpMethod;
|
|
330
|
+
exports.isSignatureRequest = isSignatureRequest;
|
|
331
|
+
exports.isTransactionProposal = isTransactionProposal;
|
|
275
332
|
exports.verifyContextRequest = verifyContextRequest;
|
|
333
|
+
exports.wrapHandshakeResponse = wrapHandshakeResponse;
|
|
276
334
|
//# sourceMappingURL=index.cjs.map
|
|
277
335
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client/types.ts","../src/client/resources/discovery.ts","../src/client/resources/tools.ts","../src/client/client.ts","../src/context/index.ts","../src/auth/index.ts"],"names":["importSPKI","jwtVerify"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Configuration options for initializing the ContextClient\n */\nexport interface ContextClientOptions {\n /**\n * Your Context Protocol API key\n * @example \"sk_live_abc123...\"\n */\n apiKey: string;\n\n /**\n * Base URL for the Context Protocol API\n * @default \"https://ctxprotocol.com\"\n */\n baseUrl?: string;\n}\n\n/**\n * An individual MCP tool exposed by a tool listing\n */\nexport interface McpTool {\n /** Name of the MCP tool method */\n name: string;\n\n /** Description of what this method does */\n description: string;\n\n /**\n * JSON Schema for the input arguments this tool accepts.\n * Used by LLMs to generate correct arguments.\n */\n inputSchema?: Record<string, unknown>;\n\n /**\n * JSON Schema for the output this tool returns.\n * Used by LLMs to understand the response structure.\n */\n outputSchema?: Record<string, unknown>;\n}\n\n/**\n * Represents a tool available on the Context Protocol marketplace\n */\nexport interface Tool {\n /** Unique identifier for the tool (UUID) */\n id: string;\n\n /** Human-readable name of the tool */\n name: string;\n\n /** Description of what the tool does */\n description: string;\n\n /** Price per execution in USDC */\n price: string;\n\n /** Tool category (e.g., \"defi\", \"nft\") */\n category?: string;\n\n /** Whether the tool is verified by Context Protocol */\n isVerified?: boolean;\n\n /**\n * Available MCP tool methods\n * Use items from this array as `toolName` when executing\n */\n mcpTools?: McpTool[];\n\n /** Creation timestamp */\n createdAt?: string;\n\n /** Last update timestamp */\n updatedAt?: string;\n}\n\n/**\n * Response from the tools search endpoint\n */\nexport interface SearchResponse {\n /** Array of matching tools */\n tools: Tool[];\n\n /** The search query that was used */\n query: string;\n\n /** Total number of results */\n count: number;\n}\n\n/**\n * Options for searching tools\n */\nexport interface SearchOptions {\n /** Search query (semantic search) */\n query?: string;\n\n /** Maximum number of results (1-50, default 10) */\n limit?: number;\n}\n\n/**\n * Options for executing a tool\n */\nexport interface ExecuteOptions {\n /** The UUID of the tool to execute (from search results) */\n toolId: string;\n\n /** The specific MCP tool name to call (from tool's mcpTools array) */\n toolName: string;\n\n /** Arguments to pass to the tool */\n args?: Record<string, unknown>;\n}\n\n/**\n * Successful execution response from the API\n */\nexport interface ExecuteApiSuccessResponse {\n success: true;\n\n /** The result data from the tool execution */\n result: unknown;\n\n /** Information about the executed tool */\n tool: {\n id: string;\n name: string;\n };\n\n /** Execution duration in milliseconds */\n durationMs: number;\n}\n\n/**\n * Error response from the API\n */\nexport interface ExecuteApiErrorResponse {\n /** Human-readable error message */\n error: string;\n\n /** Error code for programmatic handling */\n code?: ContextErrorCode;\n\n /** URL to help resolve the issue */\n helpUrl?: string;\n}\n\n/**\n * Raw API response from the execute endpoint\n */\nexport type ExecuteApiResponse = ExecuteApiSuccessResponse | ExecuteApiErrorResponse;\n\n/**\n * The resolved result returned to the user after SDK processing\n */\nexport interface ExecutionResult<T = unknown> {\n /** The data returned by the tool */\n result: T;\n\n /** Information about the executed tool */\n tool: {\n id: string;\n name: string;\n };\n\n /** Execution duration in milliseconds */\n durationMs: number;\n}\n\n/**\n * Specific error codes returned by the Context Protocol API\n */\nexport type ContextErrorCode =\n | \"unauthorized\"\n | \"no_wallet\"\n | \"insufficient_allowance\"\n | \"payment_failed\"\n | \"execution_failed\";\n\n/**\n * Error thrown by the Context Protocol client\n */\nexport class ContextError extends Error {\n constructor(\n message: string,\n public readonly code?: ContextErrorCode | string,\n public readonly statusCode?: number,\n public readonly helpUrl?: string\n ) {\n super(message);\n this.name = \"ContextError\";\n }\n}\n","import type { Tool, SearchResponse } from \"../types.js\";\nimport type { ContextClient } from \"../client.js\";\n\n/**\n * Discovery resource for searching and finding tools on the Context Protocol marketplace\n */\nexport class Discovery {\n constructor(private client: ContextClient) {}\n\n /**\n * Search for tools matching a query string\n *\n * @param query - The search query (e.g., \"gas prices\", \"nft metadata\")\n * @param limit - Maximum number of results (1-50, default 10)\n * @returns Array of matching tools\n *\n * @example\n * ```typescript\n * const tools = await client.discovery.search(\"gas prices\");\n * console.log(tools[0].name); // \"Gas Price Oracle\"\n * console.log(tools[0].mcpTools); // Available methods\n * ```\n */\n async search(query: string, limit?: number): Promise<Tool[]> {\n const params = new URLSearchParams();\n\n if (query) {\n params.set(\"q\", query);\n }\n\n if (limit !== undefined) {\n params.set(\"limit\", String(limit));\n }\n\n const queryString = params.toString();\n const endpoint = `/api/v1/tools/search${queryString ? `?${queryString}` : \"\"}`;\n\n const response = await this.client.fetch<SearchResponse>(endpoint);\n\n return response.tools;\n }\n\n /**\n * Get featured/popular tools (empty query search)\n *\n * @param limit - Maximum number of results (1-50, default 10)\n * @returns Array of featured tools\n *\n * @example\n * ```typescript\n * const featured = await client.discovery.getFeatured(5);\n * ```\n */\n async getFeatured(limit?: number): Promise<Tool[]> {\n return this.search(\"\", limit);\n }\n}\n","import type {\n ExecuteOptions,\n ExecuteApiResponse,\n ExecutionResult,\n} from \"../types.js\";\nimport { ContextError } from \"../types.js\";\nimport type { ContextClient } from \"../client.js\";\n\n/**\n * Tools resource for executing tools on the Context Protocol marketplace\n */\nexport class Tools {\n constructor(private client: ContextClient) {}\n\n /**\n * Execute a tool with the provided arguments\n *\n * @param options - Execution options\n * @param options.toolId - The UUID of the tool (from search results)\n * @param options.toolName - The specific MCP tool method to call (from tool's mcpTools array)\n * @param options.args - Arguments to pass to the tool\n * @returns The execution result with the tool's output data\n *\n * @throws {ContextError} With code `no_wallet` if wallet not set up\n * @throws {ContextError} With code `insufficient_allowance` if Auto Pay not enabled\n * @throws {ContextError} With code `payment_failed` if on-chain payment fails\n * @throws {ContextError} With code `execution_failed` if tool execution fails\n *\n * @example\n * ```typescript\n * // First, search for a tool\n * const tools = await client.discovery.search(\"gas prices\");\n * const tool = tools[0];\n *\n * // Execute a specific method from the tool's mcpTools\n * const result = await client.tools.execute({\n * toolId: tool.id,\n * toolName: tool.mcpTools[0].name, // e.g., \"get_gas_prices\"\n * args: { chainId: 1 }\n * });\n *\n * console.log(result.result); // The tool's output\n * console.log(result.durationMs); // Execution time\n * ```\n */\n async execute<T = unknown>(options: ExecuteOptions): Promise<ExecutionResult<T>> {\n const { toolId, toolName, args } = options;\n\n const response = await this.client.fetch<ExecuteApiResponse>(\n \"/api/v1/tools/execute\",\n {\n method: \"POST\",\n body: JSON.stringify({ toolId, toolName, args }),\n }\n );\n\n // Handle error response\n if (\"error\" in response) {\n throw new ContextError(\n response.error,\n response.code,\n 400,\n response.helpUrl\n );\n }\n\n // Handle success response\n if (response.success) {\n return {\n result: response.result as T,\n tool: response.tool,\n durationMs: response.durationMs,\n };\n }\n\n // Fallback - shouldn't reach here with valid API responses\n throw new ContextError(\"Unexpected response format from API\");\n }\n}\n","import type { ContextClientOptions } from \"./types.js\";\nimport { ContextError } from \"./types.js\";\nimport { Discovery } from \"./resources/discovery.js\";\nimport { Tools } from \"./resources/tools.js\";\n\n/**\n * The official TypeScript client for the Context Protocol.\n *\n * Use this client to discover and execute AI tools programmatically.\n *\n * @example\n * ```typescript\n * import { ContextClient } from \"@contextprotocol/client\";\n *\n * const client = new ContextClient({\n * apiKey: \"sk_live_...\"\n * });\n *\n * // Discover tools\n * const tools = await client.discovery.search(\"gas prices\");\n *\n * // Execute a tool method\n * const result = await client.tools.execute({\n * toolId: tools[0].id,\n * toolName: tools[0].mcpTools[0].name,\n * args: { chainId: 1 }\n * });\n * ```\n */\nexport class ContextClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n /**\n * Discovery resource for searching tools\n */\n public readonly discovery: Discovery;\n\n /**\n * Tools resource for executing tools\n */\n public readonly tools: Tools;\n\n /**\n * Creates a new Context Protocol client\n *\n * @param options - Client configuration options\n * @param options.apiKey - Your Context Protocol API key (format: sk_live_...)\n * @param options.baseUrl - Optional base URL override (defaults to https://ctxprotocol.com)\n */\n constructor(options: ContextClientOptions) {\n if (!options.apiKey) {\n throw new ContextError(\"API key is required\");\n }\n\n this.apiKey = options.apiKey;\n this.baseUrl = (options.baseUrl ?? \"https://ctxprotocol.com\").replace(/\\/$/, \"\");\n\n // Initialize resources\n this.discovery = new Discovery(this);\n this.tools = new Tools(this);\n }\n\n /**\n * Internal method for making authenticated HTTP requests\n * All requests include the Authorization header with the API key\n *\n * @internal\n */\n async fetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`;\n\n const response = await fetch(url, {\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n let errorMessage = `HTTP ${response.status}: ${response.statusText}`;\n let errorCode: string | undefined;\n let helpUrl: string | undefined;\n\n try {\n const errorBody = await response.json();\n if (errorBody.error) {\n errorMessage = errorBody.error;\n errorCode = errorBody.code;\n helpUrl = errorBody.helpUrl;\n }\n } catch {\n // Use default error message if JSON parsing fails\n }\n\n throw new ContextError(errorMessage, errorCode, response.status, helpUrl);\n }\n\n return response.json() as Promise<T>;\n }\n}\n","/**\n * Context types for portfolio and protocol data injection.\n *\n * These types allow MCP tools to receive personalized user context\n * (wallet addresses, positions, balances) for analysis.\n *\n * =============================================================================\n * DECLARING CONTEXT REQUIREMENTS\n * =============================================================================\n *\n * Since the MCP protocol only transmits standard fields (name, description,\n * inputSchema, outputSchema), context requirements MUST be embedded in the\n * inputSchema using the \"x-context-requirements\" JSON Schema extension.\n *\n * @example\n * ```typescript\n * import { CONTEXT_REQUIREMENTS_KEY, type ContextRequirementType } from \"@ctxprotocol/sdk\";\n * import type { HyperliquidContext } from \"@ctxprotocol/sdk\";\n *\n * const tool = {\n * name: \"analyze_my_positions\",\n * inputSchema: {\n * type: \"object\",\n * [CONTEXT_REQUIREMENTS_KEY]: [\"hyperliquid\"] as ContextRequirementType[],\n * properties: {\n * portfolio: { type: \"object\" }\n * },\n * required: [\"portfolio\"]\n * }\n * };\n *\n * // Your handler receives the injected context:\n * function handleAnalyzeMyPositions(args: { portfolio: HyperliquidContext }) {\n * const { perpPositions, accountSummary } = args.portfolio;\n * // ... analyze and return insights\n * }\n * ```\n *\n * @packageDocumentation\n */\n\n// Wallet context types\nexport * from \"./wallet.js\";\n\n// Protocol-specific context types\nexport * from \"./polymarket.js\";\nexport * from \"./hyperliquid.js\";\n\n// Re-import for composite type\nimport type { WalletContext, ERC20Context } from \"./wallet.js\";\nimport type { PolymarketContext } from \"./polymarket.js\";\nimport type { HyperliquidContext } from \"./hyperliquid.js\";\n\n// ============================================================================\n// CONTEXT REQUIREMENTS\n//\n// MCP tools that need user portfolio data MUST declare this in inputSchema.\n// The MCP protocol only transmits standard fields (name, description,\n// inputSchema, outputSchema). Custom fields get stripped by the MCP SDK.\n// ============================================================================\n\n/**\n * JSON Schema extension key for declaring context requirements.\n *\n * WHY THIS APPROACH?\n * - MCP protocol only transmits: name, description, inputSchema, outputSchema\n * - Custom fields like `requirements` get stripped by MCP SDK during transport\n * - JSON Schema allows custom \"x-\" prefixed extension properties\n * - inputSchema is preserved end-to-end through MCP transport\n *\n * @example\n * ```typescript\n * import { CONTEXT_REQUIREMENTS_KEY } from \"@ctxprotocol/sdk\";\n *\n * const tool = {\n * name: \"analyze_my_positions\",\n * inputSchema: {\n * type: \"object\",\n * [CONTEXT_REQUIREMENTS_KEY]: [\"hyperliquid\"],\n * properties: { portfolio: { type: \"object\" } },\n * required: [\"portfolio\"]\n * }\n * };\n * ```\n */\nexport const CONTEXT_REQUIREMENTS_KEY = \"x-context-requirements\" as const;\n\n/**\n * Context requirement types supported by the Context marketplace.\n * Maps to protocol-specific context builders on the platform.\n *\n * @example\n * ```typescript\n * inputSchema: {\n * type: \"object\",\n * \"x-context-requirements\": [\"hyperliquid\"] as ContextRequirementType[],\n * properties: { portfolio: { type: \"object\" } },\n * required: [\"portfolio\"]\n * }\n * ```\n */\nexport type ContextRequirementType = \"polymarket\" | \"hyperliquid\" | \"wallet\";\n\n/**\n * @deprecated The `requirements` field at tool level gets stripped by MCP SDK.\n * Use `x-context-requirements` inside `inputSchema` instead.\n *\n * @example\n * ```typescript\n * // ❌ OLD (doesn't work - stripped by MCP SDK)\n * { requirements: { context: [\"hyperliquid\"] } }\n *\n * // ✅ NEW (works - preserved through MCP transport)\n * { inputSchema: { \"x-context-requirements\": [\"hyperliquid\"], ... } }\n * ```\n */\nexport interface ToolRequirements {\n /**\n * @deprecated Use `x-context-requirements` in inputSchema instead.\n */\n context?: ContextRequirementType[];\n}\n\n/**\n * Composite context for tools that need multiple data sources.\n *\n * This is the unified structure that can be passed to MCP tools\n * to provide comprehensive user context.\n */\nexport interface UserContext {\n /** Base wallet information */\n wallet?: WalletContext;\n /** ERC20 token holdings */\n erc20?: ERC20Context;\n /** Polymarket positions and orders */\n polymarket?: PolymarketContext;\n /** Hyperliquid perpetual positions and account data */\n hyperliquid?: HyperliquidContext;\n // Future protocols:\n // aave?: AaveContext;\n}\n","import { jwtVerify, importSPKI, type JWTPayload } from \"jose\";\nimport { ContextError } from \"../client/types.js\";\n\n// ============================================================================\n// Express-compatible types (avoid requiring express as a dependency)\n// ============================================================================\n\ninterface ContextRequest {\n headers: {\n authorization?: string;\n [key: string]: string | string[] | undefined;\n };\n body?: {\n method?: string;\n [key: string]: unknown;\n };\n context?: JWTPayload;\n}\n\ninterface ContextResponse {\n status(code: number): ContextResponse;\n json(data: unknown): void;\n}\n\ntype NextFunction = (error?: unknown) => void;\n\n/**\n * Extended Request object with verified Context Protocol JWT payload.\n *\n * After `createContextMiddleware()` runs successfully on a protected method,\n * the `context` property contains the decoded JWT claims.\n */\nexport interface ContextMiddlewareRequest extends ContextRequest {\n /** The verified JWT payload from Context Protocol (available after auth) */\n context?: JWTPayload;\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n// The Context Protocol Public Key\n// In a real scenario, this might be fetched from a well-known URL or passed in config.\n// For now, we hardcode the Official Platform Public Key.\n// Official Context Protocol Platform Public Key (RS256)\nconst CONTEXT_PLATFORM_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs9YOgdpkmVQ5aoNovjsu\nchJdV54OT7dUdbVXz914a7Px8EwnpDqhsvG7WO8xL8sj2Rn6ueAJBk+04Hy/P/UN\nRJyp23XL5TsGmb4rbfg0ii0MiL2nbVXuqvAe3JSM2BOFZR5bpwIVIaa8aonfamUy\nVXGc7OosF90ThdKjm9cXlVM+kV6IgSWc1502X7M3abQqRcTU/rluVXnky0eiWDQa\nlfOKbr7w0u72dZjiZPwnNDsX6PEEgvfmoautTFYTQgnZjDzq8UimTcv3KF+hJ5Ep\nweipe6amt9lzQzi8WXaFKpOXHQs//WDlUytz/Hl8pvd5craZKzo6Kyrg1Vfan7H3\nTQIDAQAB\n-----END PUBLIC KEY-----`;\n\n/**\n * MCP methods that require authentication.\n * - tools/call: Executes tool logic, may cost money\n * - resources/read: Reads potentially sensitive data\n * - prompts/get: Gets prompt content\n */\nconst PROTECTED_MCP_METHODS = new Set([\n \"tools/call\",\n // Uncomment these if you want to protect resource/prompt access:\n // \"resources/read\",\n // \"prompts/get\",\n]);\n\n/**\n * MCP methods that are always open (no auth required).\n * These are discovery/listing operations that return metadata only.\n */\nconst OPEN_MCP_METHODS = new Set([\n \"initialize\",\n \"tools/list\",\n \"resources/list\",\n \"prompts/list\",\n \"ping\",\n \"notifications/initialized\",\n]);\n\n// ============================================================================\n// Method Classification\n// ============================================================================\n\n/**\n * Determines if a given MCP method requires authentication.\n *\n * Discovery methods (tools/list, resources/list, etc.) are open.\n * Execution methods (tools/call) require authentication.\n *\n * @param method The MCP JSON-RPC method (e.g., \"tools/list\", \"tools/call\")\n * @returns true if the method requires authentication\n *\n * @example\n * ```typescript\n * if (isProtectedMcpMethod(body.method)) {\n * await verifyContextRequest({ authorizationHeader: req.headers.authorization });\n * }\n * ```\n */\nexport function isProtectedMcpMethod(method: string): boolean {\n return PROTECTED_MCP_METHODS.has(method);\n}\n\n/**\n * Determines if a given MCP method is explicitly open (no auth).\n *\n * @param method The MCP JSON-RPC method\n * @returns true if the method is known to be open\n */\nexport function isOpenMcpMethod(method: string): boolean {\n return OPEN_MCP_METHODS.has(method);\n}\n\n// ============================================================================\n// Request Verification\n// ============================================================================\n\nexport interface VerifyRequestOptions {\n /** The full Authorization header string (e.g. \"Bearer eyJ...\") */\n authorizationHeader?: string;\n /** Expected Audience (your tool URL) for stricter validation */\n audience?: string;\n}\n\n/**\n * Verifies that an incoming request originated from the Context Protocol Platform.\n *\n * @param options Contains the Authorization header\n * @returns The decoded payload if valid\n * @throws ContextError if invalid\n */\nexport async function verifyContextRequest(options: VerifyRequestOptions) {\n const { authorizationHeader, audience } = options;\n\n if (!authorizationHeader || !authorizationHeader.startsWith(\"Bearer \")) {\n throw new ContextError(\n \"Missing or invalid Authorization header\",\n \"unauthorized\",\n 401\n );\n }\n\n const token = authorizationHeader.split(\" \")[1];\n\n try {\n const publicKey = await importSPKI(CONTEXT_PLATFORM_PUBLIC_KEY_PEM, \"RS256\");\n\n const { payload } = await jwtVerify(token, publicKey, {\n issuer: \"https://ctxprotocol.com\",\n audience: audience,\n });\n\n return payload;\n } catch (error) {\n throw new ContextError(\n \"Invalid Context Protocol signature\",\n \"unauthorized\",\n 401\n );\n }\n}\n\n// ============================================================================\n// Easy-Mode Middleware\n// ============================================================================\n\nexport interface CreateContextMiddlewareOptions {\n /** Expected Audience (your tool URL) for stricter validation */\n audience?: string;\n}\n\n/**\n * Creates an Express/Connect-compatible middleware that secures your MCP endpoint.\n *\n * This is the \"1 line of code\" solution to secure your MCP server.\n * It automatically:\n * - Allows discovery methods (tools/list, initialize) without authentication\n * - Requires and verifies JWT for execution methods (tools/call)\n * - Attaches the verified payload to `req.context` for downstream use\n *\n * @param options Optional configuration\n * @returns Express-compatible middleware function\n *\n * @example\n * ```typescript\n * import express from \"express\";\n * import { createContextMiddleware } from \"@ctxprotocol/sdk\";\n *\n * const app = express();\n * app.use(express.json());\n *\n * // 1 line to secure your endpoint\n * app.use(\"/mcp\", createContextMiddleware());\n *\n * app.post(\"/mcp\", (req, res) => {\n * // req.context contains verified JWT payload (on protected methods)\n * // Handle MCP request...\n * });\n * ```\n */\nexport function createContextMiddleware(options: CreateContextMiddlewareOptions = {}) {\n return async function contextMiddleware(\n req: ContextRequest,\n res: ContextResponse,\n next: NextFunction\n ): Promise<void> {\n const method = req.body?.method as string | undefined;\n\n // Allow discovery methods without authentication\n // Discovery methods (tools/list, initialize, etc.) are open by design\n if (!method || !isProtectedMcpMethod(method)) {\n return next();\n }\n\n // Protected method - require authentication\n try {\n const payload = await verifyContextRequest({\n authorizationHeader: req.headers.authorization,\n audience: options.audience,\n });\n\n // Attach verified payload to request for downstream handlers\n req.context = payload;\n next();\n } catch (error) {\n const statusCode = error instanceof ContextError ? error.statusCode || 401 : 401;\n res.status(statusCode).json({ error: \"Unauthorized\" });\n }\n };\n}\n\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/client/types.ts","../src/client/resources/discovery.ts","../src/client/resources/tools.ts","../src/client/client.ts","../src/context/index.ts","../src/auth/index.ts","../src/handshake/types.ts"],"names":["importSPKI","jwtVerify"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Configuration options for initializing the ContextClient\n */\nexport interface ContextClientOptions {\n /**\n * Your Context Protocol API key\n * @example \"sk_live_abc123...\"\n */\n apiKey: string;\n\n /**\n * Base URL for the Context Protocol API\n * @default \"https://ctxprotocol.com\"\n */\n baseUrl?: string;\n}\n\n/**\n * An individual MCP tool exposed by a tool listing\n */\nexport interface McpTool {\n /** Name of the MCP tool method */\n name: string;\n\n /** Description of what this method does */\n description: string;\n\n /**\n * JSON Schema for the input arguments this tool accepts.\n * Used by LLMs to generate correct arguments.\n */\n inputSchema?: Record<string, unknown>;\n\n /**\n * JSON Schema for the output this tool returns.\n * Used by LLMs to understand the response structure.\n */\n outputSchema?: Record<string, unknown>;\n}\n\n/**\n * Represents a tool available on the Context Protocol marketplace\n */\nexport interface Tool {\n /** Unique identifier for the tool (UUID) */\n id: string;\n\n /** Human-readable name of the tool */\n name: string;\n\n /** Description of what the tool does */\n description: string;\n\n /** Price per execution in USDC */\n price: string;\n\n /** Tool category (e.g., \"defi\", \"nft\") */\n category?: string;\n\n /** Whether the tool is verified by Context Protocol */\n isVerified?: boolean;\n\n /**\n * Available MCP tool methods\n * Use items from this array as `toolName` when executing\n */\n mcpTools?: McpTool[];\n\n /** Creation timestamp */\n createdAt?: string;\n\n /** Last update timestamp */\n updatedAt?: string;\n}\n\n/**\n * Response from the tools search endpoint\n */\nexport interface SearchResponse {\n /** Array of matching tools */\n tools: Tool[];\n\n /** The search query that was used */\n query: string;\n\n /** Total number of results */\n count: number;\n}\n\n/**\n * Options for searching tools\n */\nexport interface SearchOptions {\n /** Search query (semantic search) */\n query?: string;\n\n /** Maximum number of results (1-50, default 10) */\n limit?: number;\n}\n\n/**\n * Options for executing a tool\n */\nexport interface ExecuteOptions {\n /** The UUID of the tool to execute (from search results) */\n toolId: string;\n\n /** The specific MCP tool name to call (from tool's mcpTools array) */\n toolName: string;\n\n /** Arguments to pass to the tool */\n args?: Record<string, unknown>;\n}\n\n/**\n * Successful execution response from the API\n */\nexport interface ExecuteApiSuccessResponse {\n success: true;\n\n /** The result data from the tool execution */\n result: unknown;\n\n /** Information about the executed tool */\n tool: {\n id: string;\n name: string;\n };\n\n /** Execution duration in milliseconds */\n durationMs: number;\n}\n\n/**\n * Error response from the API\n */\nexport interface ExecuteApiErrorResponse {\n /** Human-readable error message */\n error: string;\n\n /** Error code for programmatic handling */\n code?: ContextErrorCode;\n\n /** URL to help resolve the issue */\n helpUrl?: string;\n}\n\n/**\n * Raw API response from the execute endpoint\n */\nexport type ExecuteApiResponse = ExecuteApiSuccessResponse | ExecuteApiErrorResponse;\n\n/**\n * The resolved result returned to the user after SDK processing\n */\nexport interface ExecutionResult<T = unknown> {\n /** The data returned by the tool */\n result: T;\n\n /** Information about the executed tool */\n tool: {\n id: string;\n name: string;\n };\n\n /** Execution duration in milliseconds */\n durationMs: number;\n}\n\n/**\n * Specific error codes returned by the Context Protocol API\n */\nexport type ContextErrorCode =\n | \"unauthorized\"\n | \"no_wallet\"\n | \"insufficient_allowance\"\n | \"payment_failed\"\n | \"execution_failed\";\n\n/**\n * Error thrown by the Context Protocol client\n */\nexport class ContextError extends Error {\n constructor(\n message: string,\n public readonly code?: ContextErrorCode | string,\n public readonly statusCode?: number,\n public readonly helpUrl?: string\n ) {\n super(message);\n this.name = \"ContextError\";\n }\n}\n","import type { Tool, SearchResponse } from \"../types.js\";\nimport type { ContextClient } from \"../client.js\";\n\n/**\n * Discovery resource for searching and finding tools on the Context Protocol marketplace\n */\nexport class Discovery {\n constructor(private client: ContextClient) {}\n\n /**\n * Search for tools matching a query string\n *\n * @param query - The search query (e.g., \"gas prices\", \"nft metadata\")\n * @param limit - Maximum number of results (1-50, default 10)\n * @returns Array of matching tools\n *\n * @example\n * ```typescript\n * const tools = await client.discovery.search(\"gas prices\");\n * console.log(tools[0].name); // \"Gas Price Oracle\"\n * console.log(tools[0].mcpTools); // Available methods\n * ```\n */\n async search(query: string, limit?: number): Promise<Tool[]> {\n const params = new URLSearchParams();\n\n if (query) {\n params.set(\"q\", query);\n }\n\n if (limit !== undefined) {\n params.set(\"limit\", String(limit));\n }\n\n const queryString = params.toString();\n const endpoint = `/api/v1/tools/search${queryString ? `?${queryString}` : \"\"}`;\n\n const response = await this.client.fetch<SearchResponse>(endpoint);\n\n return response.tools;\n }\n\n /**\n * Get featured/popular tools (empty query search)\n *\n * @param limit - Maximum number of results (1-50, default 10)\n * @returns Array of featured tools\n *\n * @example\n * ```typescript\n * const featured = await client.discovery.getFeatured(5);\n * ```\n */\n async getFeatured(limit?: number): Promise<Tool[]> {\n return this.search(\"\", limit);\n }\n}\n","import type {\n ExecuteOptions,\n ExecuteApiResponse,\n ExecutionResult,\n} from \"../types.js\";\nimport { ContextError } from \"../types.js\";\nimport type { ContextClient } from \"../client.js\";\n\n/**\n * Tools resource for executing tools on the Context Protocol marketplace\n */\nexport class Tools {\n constructor(private client: ContextClient) {}\n\n /**\n * Execute a tool with the provided arguments\n *\n * @param options - Execution options\n * @param options.toolId - The UUID of the tool (from search results)\n * @param options.toolName - The specific MCP tool method to call (from tool's mcpTools array)\n * @param options.args - Arguments to pass to the tool\n * @returns The execution result with the tool's output data\n *\n * @throws {ContextError} With code `no_wallet` if wallet not set up\n * @throws {ContextError} With code `insufficient_allowance` if Auto Pay not enabled\n * @throws {ContextError} With code `payment_failed` if on-chain payment fails\n * @throws {ContextError} With code `execution_failed` if tool execution fails\n *\n * @example\n * ```typescript\n * // First, search for a tool\n * const tools = await client.discovery.search(\"gas prices\");\n * const tool = tools[0];\n *\n * // Execute a specific method from the tool's mcpTools\n * const result = await client.tools.execute({\n * toolId: tool.id,\n * toolName: tool.mcpTools[0].name, // e.g., \"get_gas_prices\"\n * args: { chainId: 1 }\n * });\n *\n * console.log(result.result); // The tool's output\n * console.log(result.durationMs); // Execution time\n * ```\n */\n async execute<T = unknown>(options: ExecuteOptions): Promise<ExecutionResult<T>> {\n const { toolId, toolName, args } = options;\n\n const response = await this.client.fetch<ExecuteApiResponse>(\n \"/api/v1/tools/execute\",\n {\n method: \"POST\",\n body: JSON.stringify({ toolId, toolName, args }),\n }\n );\n\n // Handle error response\n if (\"error\" in response) {\n throw new ContextError(\n response.error,\n response.code,\n 400,\n response.helpUrl\n );\n }\n\n // Handle success response\n if (response.success) {\n return {\n result: response.result as T,\n tool: response.tool,\n durationMs: response.durationMs,\n };\n }\n\n // Fallback - shouldn't reach here with valid API responses\n throw new ContextError(\"Unexpected response format from API\");\n }\n}\n","import type { ContextClientOptions } from \"./types.js\";\nimport { ContextError } from \"./types.js\";\nimport { Discovery } from \"./resources/discovery.js\";\nimport { Tools } from \"./resources/tools.js\";\n\n/**\n * The official TypeScript client for the Context Protocol.\n *\n * Use this client to discover and execute AI tools programmatically.\n *\n * @example\n * ```typescript\n * import { ContextClient } from \"@contextprotocol/client\";\n *\n * const client = new ContextClient({\n * apiKey: \"sk_live_...\"\n * });\n *\n * // Discover tools\n * const tools = await client.discovery.search(\"gas prices\");\n *\n * // Execute a tool method\n * const result = await client.tools.execute({\n * toolId: tools[0].id,\n * toolName: tools[0].mcpTools[0].name,\n * args: { chainId: 1 }\n * });\n * ```\n */\nexport class ContextClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n /**\n * Discovery resource for searching tools\n */\n public readonly discovery: Discovery;\n\n /**\n * Tools resource for executing tools\n */\n public readonly tools: Tools;\n\n /**\n * Creates a new Context Protocol client\n *\n * @param options - Client configuration options\n * @param options.apiKey - Your Context Protocol API key (format: sk_live_...)\n * @param options.baseUrl - Optional base URL override (defaults to https://ctxprotocol.com)\n */\n constructor(options: ContextClientOptions) {\n if (!options.apiKey) {\n throw new ContextError(\"API key is required\");\n }\n\n this.apiKey = options.apiKey;\n this.baseUrl = (options.baseUrl ?? \"https://ctxprotocol.com\").replace(/\\/$/, \"\");\n\n // Initialize resources\n this.discovery = new Discovery(this);\n this.tools = new Tools(this);\n }\n\n /**\n * Internal method for making authenticated HTTP requests\n * All requests include the Authorization header with the API key\n *\n * @internal\n */\n async fetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`;\n\n const response = await fetch(url, {\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n let errorMessage = `HTTP ${response.status}: ${response.statusText}`;\n let errorCode: string | undefined;\n let helpUrl: string | undefined;\n\n try {\n const errorBody = await response.json();\n if (errorBody.error) {\n errorMessage = errorBody.error;\n errorCode = errorBody.code;\n helpUrl = errorBody.helpUrl;\n }\n } catch {\n // Use default error message if JSON parsing fails\n }\n\n throw new ContextError(errorMessage, errorCode, response.status, helpUrl);\n }\n\n return response.json() as Promise<T>;\n }\n}\n","/**\n * Context types for portfolio and protocol data injection.\n *\n * These types allow MCP tools to receive personalized user context\n * (wallet addresses, positions, balances) for analysis.\n *\n * =============================================================================\n * DECLARING CONTEXT REQUIREMENTS\n * =============================================================================\n *\n * Since the MCP protocol only transmits standard fields (name, description,\n * inputSchema, outputSchema), context requirements MUST be embedded in the\n * inputSchema using the \"x-context-requirements\" JSON Schema extension.\n *\n * @example\n * ```typescript\n * import { CONTEXT_REQUIREMENTS_KEY, type ContextRequirementType } from \"@ctxprotocol/sdk\";\n * import type { HyperliquidContext } from \"@ctxprotocol/sdk\";\n *\n * const tool = {\n * name: \"analyze_my_positions\",\n * inputSchema: {\n * type: \"object\",\n * [CONTEXT_REQUIREMENTS_KEY]: [\"hyperliquid\"] as ContextRequirementType[],\n * properties: {\n * portfolio: { type: \"object\" }\n * },\n * required: [\"portfolio\"]\n * }\n * };\n *\n * // Your handler receives the injected context:\n * function handleAnalyzeMyPositions(args: { portfolio: HyperliquidContext }) {\n * const { perpPositions, accountSummary } = args.portfolio;\n * // ... analyze and return insights\n * }\n * ```\n *\n * @packageDocumentation\n */\n\n// Wallet context types\nexport * from \"./wallet.js\";\n\n// Protocol-specific context types\nexport * from \"./polymarket.js\";\nexport * from \"./hyperliquid.js\";\n\n// Re-import for composite type\nimport type { WalletContext, ERC20Context } from \"./wallet.js\";\nimport type { PolymarketContext } from \"./polymarket.js\";\nimport type { HyperliquidContext } from \"./hyperliquid.js\";\n\n// ============================================================================\n// CONTEXT REQUIREMENTS\n//\n// MCP tools that need user portfolio data MUST declare this in inputSchema.\n// The MCP protocol only transmits standard fields (name, description,\n// inputSchema, outputSchema). Custom fields get stripped by the MCP SDK.\n// ============================================================================\n\n/**\n * JSON Schema extension key for declaring context requirements.\n *\n * WHY THIS APPROACH?\n * - MCP protocol only transmits: name, description, inputSchema, outputSchema\n * - Custom fields like `requirements` get stripped by MCP SDK during transport\n * - JSON Schema allows custom \"x-\" prefixed extension properties\n * - inputSchema is preserved end-to-end through MCP transport\n *\n * @example\n * ```typescript\n * import { CONTEXT_REQUIREMENTS_KEY } from \"@ctxprotocol/sdk\";\n *\n * const tool = {\n * name: \"analyze_my_positions\",\n * inputSchema: {\n * type: \"object\",\n * [CONTEXT_REQUIREMENTS_KEY]: [\"hyperliquid\"],\n * properties: { portfolio: { type: \"object\" } },\n * required: [\"portfolio\"]\n * }\n * };\n * ```\n */\nexport const CONTEXT_REQUIREMENTS_KEY = \"x-context-requirements\" as const;\n\n/**\n * Context requirement types supported by the Context marketplace.\n * Maps to protocol-specific context builders on the platform.\n *\n * @example\n * ```typescript\n * inputSchema: {\n * type: \"object\",\n * \"x-context-requirements\": [\"hyperliquid\"] as ContextRequirementType[],\n * properties: { portfolio: { type: \"object\" } },\n * required: [\"portfolio\"]\n * }\n * ```\n */\nexport type ContextRequirementType = \"polymarket\" | \"hyperliquid\" | \"wallet\";\n\n/**\n * @deprecated The `requirements` field at tool level gets stripped by MCP SDK.\n * Use `x-context-requirements` inside `inputSchema` instead.\n *\n * @example\n * ```typescript\n * // ❌ OLD (doesn't work - stripped by MCP SDK)\n * { requirements: { context: [\"hyperliquid\"] } }\n *\n * // ✅ NEW (works - preserved through MCP transport)\n * { inputSchema: { \"x-context-requirements\": [\"hyperliquid\"], ... } }\n * ```\n */\nexport interface ToolRequirements {\n /**\n * @deprecated Use `x-context-requirements` in inputSchema instead.\n */\n context?: ContextRequirementType[];\n}\n\n/**\n * Composite context for tools that need multiple data sources.\n *\n * This is the unified structure that can be passed to MCP tools\n * to provide comprehensive user context.\n */\nexport interface UserContext {\n /** Base wallet information */\n wallet?: WalletContext;\n /** ERC20 token holdings */\n erc20?: ERC20Context;\n /** Polymarket positions and orders */\n polymarket?: PolymarketContext;\n /** Hyperliquid perpetual positions and account data */\n hyperliquid?: HyperliquidContext;\n // Future protocols:\n // aave?: AaveContext;\n}\n","import { jwtVerify, importSPKI, type JWTPayload } from \"jose\";\nimport { ContextError } from \"../client/types.js\";\n\n// ============================================================================\n// Express-compatible types (avoid requiring express as a dependency)\n// ============================================================================\n\ninterface ContextRequest {\n headers: {\n authorization?: string;\n [key: string]: string | string[] | undefined;\n };\n body?: {\n method?: string;\n [key: string]: unknown;\n };\n context?: JWTPayload;\n}\n\ninterface ContextResponse {\n status(code: number): ContextResponse;\n json(data: unknown): void;\n}\n\ntype NextFunction = (error?: unknown) => void;\n\n/**\n * Extended Request object with verified Context Protocol JWT payload.\n *\n * After `createContextMiddleware()` runs successfully on a protected method,\n * the `context` property contains the decoded JWT claims.\n */\nexport interface ContextMiddlewareRequest extends ContextRequest {\n /** The verified JWT payload from Context Protocol (available after auth) */\n context?: JWTPayload;\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n// The Context Protocol Public Key\n// In a real scenario, this might be fetched from a well-known URL or passed in config.\n// For now, we hardcode the Official Platform Public Key.\n// Official Context Protocol Platform Public Key (RS256)\nconst CONTEXT_PLATFORM_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs9YOgdpkmVQ5aoNovjsu\nchJdV54OT7dUdbVXz914a7Px8EwnpDqhsvG7WO8xL8sj2Rn6ueAJBk+04Hy/P/UN\nRJyp23XL5TsGmb4rbfg0ii0MiL2nbVXuqvAe3JSM2BOFZR5bpwIVIaa8aonfamUy\nVXGc7OosF90ThdKjm9cXlVM+kV6IgSWc1502X7M3abQqRcTU/rluVXnky0eiWDQa\nlfOKbr7w0u72dZjiZPwnNDsX6PEEgvfmoautTFYTQgnZjDzq8UimTcv3KF+hJ5Ep\nweipe6amt9lzQzi8WXaFKpOXHQs//WDlUytz/Hl8pvd5craZKzo6Kyrg1Vfan7H3\nTQIDAQAB\n-----END PUBLIC KEY-----`;\n\n/**\n * MCP methods that require authentication.\n * - tools/call: Executes tool logic, may cost money\n * - resources/read: Reads potentially sensitive data\n * - prompts/get: Gets prompt content\n */\nconst PROTECTED_MCP_METHODS = new Set([\n \"tools/call\",\n // Uncomment these if you want to protect resource/prompt access:\n // \"resources/read\",\n // \"prompts/get\",\n]);\n\n/**\n * MCP methods that are always open (no auth required).\n * These are discovery/listing operations that return metadata only.\n */\nconst OPEN_MCP_METHODS = new Set([\n \"initialize\",\n \"tools/list\",\n \"resources/list\",\n \"prompts/list\",\n \"ping\",\n \"notifications/initialized\",\n]);\n\n// ============================================================================\n// Method Classification\n// ============================================================================\n\n/**\n * Determines if a given MCP method requires authentication.\n *\n * Discovery methods (tools/list, resources/list, etc.) are open.\n * Execution methods (tools/call) require authentication.\n *\n * @param method The MCP JSON-RPC method (e.g., \"tools/list\", \"tools/call\")\n * @returns true if the method requires authentication\n *\n * @example\n * ```typescript\n * if (isProtectedMcpMethod(body.method)) {\n * await verifyContextRequest({ authorizationHeader: req.headers.authorization });\n * }\n * ```\n */\nexport function isProtectedMcpMethod(method: string): boolean {\n return PROTECTED_MCP_METHODS.has(method);\n}\n\n/**\n * Determines if a given MCP method is explicitly open (no auth).\n *\n * @param method The MCP JSON-RPC method\n * @returns true if the method is known to be open\n */\nexport function isOpenMcpMethod(method: string): boolean {\n return OPEN_MCP_METHODS.has(method);\n}\n\n// ============================================================================\n// Request Verification\n// ============================================================================\n\nexport interface VerifyRequestOptions {\n /** The full Authorization header string (e.g. \"Bearer eyJ...\") */\n authorizationHeader?: string;\n /** Expected Audience (your tool URL) for stricter validation */\n audience?: string;\n}\n\n/**\n * Verifies that an incoming request originated from the Context Protocol Platform.\n *\n * @param options Contains the Authorization header\n * @returns The decoded payload if valid\n * @throws ContextError if invalid\n */\nexport async function verifyContextRequest(options: VerifyRequestOptions) {\n const { authorizationHeader, audience } = options;\n\n if (!authorizationHeader || !authorizationHeader.startsWith(\"Bearer \")) {\n throw new ContextError(\n \"Missing or invalid Authorization header\",\n \"unauthorized\",\n 401\n );\n }\n\n const token = authorizationHeader.split(\" \")[1];\n\n try {\n const publicKey = await importSPKI(CONTEXT_PLATFORM_PUBLIC_KEY_PEM, \"RS256\");\n\n const { payload } = await jwtVerify(token, publicKey, {\n issuer: \"https://ctxprotocol.com\",\n audience: audience,\n });\n\n return payload;\n } catch (error) {\n throw new ContextError(\n \"Invalid Context Protocol signature\",\n \"unauthorized\",\n 401\n );\n }\n}\n\n// ============================================================================\n// Easy-Mode Middleware\n// ============================================================================\n\nexport interface CreateContextMiddlewareOptions {\n /** Expected Audience (your tool URL) for stricter validation */\n audience?: string;\n}\n\n/**\n * Creates an Express/Connect-compatible middleware that secures your MCP endpoint.\n *\n * This is the \"1 line of code\" solution to secure your MCP server.\n * It automatically:\n * - Allows discovery methods (tools/list, initialize) without authentication\n * - Requires and verifies JWT for execution methods (tools/call)\n * - Attaches the verified payload to `req.context` for downstream use\n *\n * @param options Optional configuration\n * @returns Express-compatible middleware function\n *\n * @example\n * ```typescript\n * import express from \"express\";\n * import { createContextMiddleware } from \"@ctxprotocol/sdk\";\n *\n * const app = express();\n * app.use(express.json());\n *\n * // 1 line to secure your endpoint\n * app.use(\"/mcp\", createContextMiddleware());\n *\n * app.post(\"/mcp\", (req, res) => {\n * // req.context contains verified JWT payload (on protected methods)\n * // Handle MCP request...\n * });\n * ```\n */\nexport function createContextMiddleware(options: CreateContextMiddlewareOptions = {}) {\n return async function contextMiddleware(\n req: ContextRequest,\n res: ContextResponse,\n next: NextFunction\n ): Promise<void> {\n const method = req.body?.method as string | undefined;\n\n // Allow discovery methods without authentication\n // Discovery methods (tools/list, initialize, etc.) are open by design\n if (!method || !isProtectedMcpMethod(method)) {\n return next();\n }\n\n // Protected method - require authentication\n try {\n const payload = await verifyContextRequest({\n authorizationHeader: req.headers.authorization,\n audience: options.audience,\n });\n\n // Attach verified payload to request for downstream handlers\n req.context = payload;\n next();\n } catch (error) {\n const statusCode = error instanceof ContextError ? error.statusCode || 401 : 401;\n res.status(statusCode).json({ error: \"Unauthorized\" });\n }\n };\n}\n\n\n","/**\n * Handshake Types for MCP Tool Developers\n *\n * Use these types when your tool needs to request user interaction\n * before completing an action (signatures, transactions, OAuth).\n *\n * @see https://docs.ctxprotocol.com/guides/handshake-architecture\n *\n * ## Usage Pattern\n *\n * Tools return handshake actions in the `_meta.handshakeAction` field\n * of their MCP response. The Context platform intercepts these and\n * presents the appropriate UI to the user.\n *\n * ## Action Types\n *\n * - `signature_request`: For EIP-712 signatures (Hyperliquid, Polymarket, etc.)\n * - `transaction_proposal`: For direct on-chain transactions (Uniswap, NFT mints)\n * - `auth_required`: For OAuth flows (Discord, Twitter, etc.)\n */\n\n// === Shared Meta Type ===\n\nexport type HandshakeMeta = {\n /** Human-readable description of the action */\n description: string;\n /** Protocol name (e.g., \"Hyperliquid\", \"Polymarket\") */\n protocol?: string;\n /** Action verb (e.g., \"Place Order\", \"Place Bid\") */\n action?: string;\n /** Token symbol if relevant */\n tokenSymbol?: string;\n /** Human-readable token amount */\n tokenAmount?: string;\n /** UI warning level */\n warningLevel?: \"info\" | \"caution\" | \"danger\";\n};\n\n// === Web3: Signature Requests (for proxy wallet platforms) ===\n\nexport type EIP712Domain = {\n /** Domain name (e.g., \"Hyperliquid\", \"ClobAuthDomain\") */\n name: string;\n /** Domain version */\n version: string;\n /** Chain ID (informational - signing is chain-agnostic) */\n chainId: number;\n /** Optional verifying contract address */\n verifyingContract?: `0x${string}`;\n};\n\nexport type EIP712TypeField = {\n name: string;\n type: string;\n};\n\n/**\n * Signature Request\n *\n * Use this for platforms with proxy wallets (Hyperliquid, Polymarket, dYdX).\n *\n * Benefits:\n * - No gas required (user signs a message, not a transaction)\n * - No network switching needed (signing is chain-agnostic)\n * - Works with Privy embedded wallets on any chain\n *\n * @example\n * ```typescript\n * return {\n * structuredContent: {\n * _meta: {\n * handshakeAction: createSignatureRequest({\n * domain: { name: \"Hyperliquid\", version: \"1\", chainId: 42161 },\n * types: { Order: [...] },\n * primaryType: \"Order\",\n * message: { asset: 4, isBuy: true, ... },\n * meta: { description: \"Place Long ETH order\", protocol: \"Hyperliquid\" }\n * })\n * }\n * }\n * };\n * ```\n */\nexport type SignatureRequest = {\n _action: \"signature_request\";\n /** EIP-712 domain separator */\n domain: EIP712Domain;\n /** EIP-712 type definitions */\n types: Record<string, EIP712TypeField[]>;\n /** The primary type being signed */\n primaryType: string;\n /** The message data to sign */\n message: Record<string, unknown>;\n /** UI metadata for the approval card */\n meta?: HandshakeMeta;\n /**\n * Optional: Tool name to call with the signature result.\n * If provided, the platform will call this tool with { signature, originalParams }\n * after the user signs.\n */\n callbackToolName?: string;\n};\n\n// === Web3: Transaction Proposals (for direct on-chain actions) ===\n\nexport type TransactionProposalMeta = HandshakeMeta & {\n /** Estimated gas cost (informational - Context may sponsor) */\n estimatedGas?: string;\n /** Link to contract on block explorer */\n explorerUrl?: string;\n};\n\n/**\n * Transaction Proposal\n *\n * Use this for protocols without proxy wallets (Uniswap, NFT mints, etc.).\n *\n * Note: May require network switching and gas fees.\n *\n * @example\n * ```typescript\n * return {\n * structuredContent: {\n * _meta: {\n * handshakeAction: createTransactionProposal({\n * chainId: 8453,\n * to: \"0x...\",\n * data: \"0x...\",\n * meta: { description: \"Swap 100 USDC for ETH\", protocol: \"Uniswap\" }\n * })\n * }\n * }\n * };\n * ```\n */\nexport type TransactionProposal = {\n _action: \"transaction_proposal\";\n /** EVM chain ID (e.g., 137 for Polygon, 8453 for Base) */\n chainId: number;\n /** Target contract address */\n to: `0x${string}`;\n /** Encoded calldata */\n data: `0x${string}`;\n /** Wei to send (as string, default \"0\") */\n value?: string;\n /** UI metadata for the approval card */\n meta?: TransactionProposalMeta;\n};\n\n// === Web2: OAuth Requests ===\n\nexport type AuthRequiredMeta = {\n /** Human-friendly service name */\n displayName?: string;\n /** Permissions being requested */\n scopes?: string[];\n /** Description of what access is needed */\n description?: string;\n /** Tool's icon URL */\n iconUrl?: string;\n /** How long authorization lasts */\n expiresIn?: string;\n};\n\n/**\n * Auth Required\n *\n * Use this when your tool needs the user to authenticate with an external service.\n *\n * @example\n * ```typescript\n * if (!hasUserToken(contextDid)) {\n * return {\n * structuredContent: {\n * _meta: {\n * handshakeAction: createAuthRequired({\n * provider: \"discord\",\n * authUrl: \"https://your-server.com/oauth/discord\",\n * meta: { displayName: \"Discord Bot\", scopes: [\"send_messages\"] }\n * })\n * }\n * }\n * };\n * }\n * ```\n */\nexport type AuthRequired = {\n _action: \"auth_required\";\n /** Service identifier (e.g., \"discord\", \"slack\") */\n provider: string;\n /** Your OAuth initiation endpoint (MUST be HTTPS) */\n authUrl: string;\n /** UI metadata for the auth card */\n meta?: AuthRequiredMeta;\n};\n\n// === Union Type ===\n\nexport type HandshakeAction =\n | SignatureRequest\n | TransactionProposal\n | AuthRequired;\n\n// === Type Guards ===\n\nexport function isHandshakeAction(value: unknown): value is HandshakeAction {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"_action\" in value &&\n ((value as { _action: string })._action === \"signature_request\" ||\n (value as { _action: string })._action === \"transaction_proposal\" ||\n (value as { _action: string })._action === \"auth_required\")\n );\n}\n\nexport function isSignatureRequest(value: unknown): value is SignatureRequest {\n return isHandshakeAction(value) && value._action === \"signature_request\";\n}\n\nexport function isTransactionProposal(\n value: unknown\n): value is TransactionProposal {\n return isHandshakeAction(value) && value._action === \"transaction_proposal\";\n}\n\nexport function isAuthRequired(value: unknown): value is AuthRequired {\n return isHandshakeAction(value) && value._action === \"auth_required\";\n}\n\n// === Helper Functions for Tool Developers ===\n\n/**\n * Create a signature request response.\n * Return this from your tool when you need the user to sign EIP-712 typed data.\n *\n * Use this for platforms with proxy wallets (Hyperliquid, Polymarket, dYdX).\n * Benefits: No gas required, no network switching needed.\n */\nexport function createSignatureRequest(\n params: Omit<SignatureRequest, \"_action\">\n): SignatureRequest {\n return {\n _action: \"signature_request\",\n ...params,\n };\n}\n\n/**\n * Create a transaction proposal response.\n * Return this from your tool when you need the user to sign a direct on-chain transaction.\n *\n * Use this for protocols that don't use proxy wallets (Uniswap, NFT mints, etc.).\n * Note: May require network switching and gas.\n */\nexport function createTransactionProposal(\n params: Omit<TransactionProposal, \"_action\">\n): TransactionProposal {\n return {\n _action: \"transaction_proposal\",\n ...params,\n };\n}\n\n/**\n * Create an auth required response.\n * Return this from your tool when you need the user to authenticate via OAuth.\n */\nexport function createAuthRequired(\n params: Omit<AuthRequired, \"_action\">\n): AuthRequired {\n return {\n _action: \"auth_required\",\n ...params,\n };\n}\n\n// === MCP Response Helper ===\n\n/**\n * Wrap a handshake action in the proper MCP response format.\n *\n * MCP tools should return handshake actions in `_meta.handshakeAction` to prevent\n * the MCP SDK from stripping unknown fields.\n *\n * @example\n * ```typescript\n * // In your tool handler:\n * return wrapHandshakeResponse(createSignatureRequest({\n * domain: { name: \"Hyperliquid\", version: \"1\", chainId: 42161 },\n * types: { Order: [...] },\n * primaryType: \"Order\",\n * message: orderData,\n * meta: { description: \"Place order\", protocol: \"Hyperliquid\" }\n * }));\n * ```\n */\nexport function wrapHandshakeResponse(action: HandshakeAction): {\n content: Array<{ type: \"text\"; text: string }>;\n structuredContent: {\n _meta: { handshakeAction: HandshakeAction };\n status: string;\n message: string;\n };\n} {\n const actionType = action._action.replace(\"_\", \" \");\n return {\n content: [\n {\n type: \"text\",\n text: `Handshake required: ${actionType}. Please approve in the Context app.`,\n },\n ],\n structuredContent: {\n _meta: {\n handshakeAction: action,\n },\n status: \"handshake_required\",\n message: action.meta?.description ?? `${actionType} required`,\n },\n };\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -434,4 +434,241 @@ interface CreateContextMiddlewareOptions {
|
|
|
434
434
|
*/
|
|
435
435
|
declare function createContextMiddleware(options?: CreateContextMiddlewareOptions): (req: ContextRequest, res: ContextResponse, next: NextFunction) => Promise<void>;
|
|
436
436
|
|
|
437
|
-
|
|
437
|
+
/**
|
|
438
|
+
* Handshake Types for MCP Tool Developers
|
|
439
|
+
*
|
|
440
|
+
* Use these types when your tool needs to request user interaction
|
|
441
|
+
* before completing an action (signatures, transactions, OAuth).
|
|
442
|
+
*
|
|
443
|
+
* @see https://docs.ctxprotocol.com/guides/handshake-architecture
|
|
444
|
+
*
|
|
445
|
+
* ## Usage Pattern
|
|
446
|
+
*
|
|
447
|
+
* Tools return handshake actions in the `_meta.handshakeAction` field
|
|
448
|
+
* of their MCP response. The Context platform intercepts these and
|
|
449
|
+
* presents the appropriate UI to the user.
|
|
450
|
+
*
|
|
451
|
+
* ## Action Types
|
|
452
|
+
*
|
|
453
|
+
* - `signature_request`: For EIP-712 signatures (Hyperliquid, Polymarket, etc.)
|
|
454
|
+
* - `transaction_proposal`: For direct on-chain transactions (Uniswap, NFT mints)
|
|
455
|
+
* - `auth_required`: For OAuth flows (Discord, Twitter, etc.)
|
|
456
|
+
*/
|
|
457
|
+
type HandshakeMeta = {
|
|
458
|
+
/** Human-readable description of the action */
|
|
459
|
+
description: string;
|
|
460
|
+
/** Protocol name (e.g., "Hyperliquid", "Polymarket") */
|
|
461
|
+
protocol?: string;
|
|
462
|
+
/** Action verb (e.g., "Place Order", "Place Bid") */
|
|
463
|
+
action?: string;
|
|
464
|
+
/** Token symbol if relevant */
|
|
465
|
+
tokenSymbol?: string;
|
|
466
|
+
/** Human-readable token amount */
|
|
467
|
+
tokenAmount?: string;
|
|
468
|
+
/** UI warning level */
|
|
469
|
+
warningLevel?: "info" | "caution" | "danger";
|
|
470
|
+
};
|
|
471
|
+
type EIP712Domain = {
|
|
472
|
+
/** Domain name (e.g., "Hyperliquid", "ClobAuthDomain") */
|
|
473
|
+
name: string;
|
|
474
|
+
/** Domain version */
|
|
475
|
+
version: string;
|
|
476
|
+
/** Chain ID (informational - signing is chain-agnostic) */
|
|
477
|
+
chainId: number;
|
|
478
|
+
/** Optional verifying contract address */
|
|
479
|
+
verifyingContract?: `0x${string}`;
|
|
480
|
+
};
|
|
481
|
+
type EIP712TypeField = {
|
|
482
|
+
name: string;
|
|
483
|
+
type: string;
|
|
484
|
+
};
|
|
485
|
+
/**
|
|
486
|
+
* Signature Request
|
|
487
|
+
*
|
|
488
|
+
* Use this for platforms with proxy wallets (Hyperliquid, Polymarket, dYdX).
|
|
489
|
+
*
|
|
490
|
+
* Benefits:
|
|
491
|
+
* - No gas required (user signs a message, not a transaction)
|
|
492
|
+
* - No network switching needed (signing is chain-agnostic)
|
|
493
|
+
* - Works with Privy embedded wallets on any chain
|
|
494
|
+
*
|
|
495
|
+
* @example
|
|
496
|
+
* ```typescript
|
|
497
|
+
* return {
|
|
498
|
+
* structuredContent: {
|
|
499
|
+
* _meta: {
|
|
500
|
+
* handshakeAction: createSignatureRequest({
|
|
501
|
+
* domain: { name: "Hyperliquid", version: "1", chainId: 42161 },
|
|
502
|
+
* types: { Order: [...] },
|
|
503
|
+
* primaryType: "Order",
|
|
504
|
+
* message: { asset: 4, isBuy: true, ... },
|
|
505
|
+
* meta: { description: "Place Long ETH order", protocol: "Hyperliquid" }
|
|
506
|
+
* })
|
|
507
|
+
* }
|
|
508
|
+
* }
|
|
509
|
+
* };
|
|
510
|
+
* ```
|
|
511
|
+
*/
|
|
512
|
+
type SignatureRequest = {
|
|
513
|
+
_action: "signature_request";
|
|
514
|
+
/** EIP-712 domain separator */
|
|
515
|
+
domain: EIP712Domain;
|
|
516
|
+
/** EIP-712 type definitions */
|
|
517
|
+
types: Record<string, EIP712TypeField[]>;
|
|
518
|
+
/** The primary type being signed */
|
|
519
|
+
primaryType: string;
|
|
520
|
+
/** The message data to sign */
|
|
521
|
+
message: Record<string, unknown>;
|
|
522
|
+
/** UI metadata for the approval card */
|
|
523
|
+
meta?: HandshakeMeta;
|
|
524
|
+
/**
|
|
525
|
+
* Optional: Tool name to call with the signature result.
|
|
526
|
+
* If provided, the platform will call this tool with { signature, originalParams }
|
|
527
|
+
* after the user signs.
|
|
528
|
+
*/
|
|
529
|
+
callbackToolName?: string;
|
|
530
|
+
};
|
|
531
|
+
type TransactionProposalMeta = HandshakeMeta & {
|
|
532
|
+
/** Estimated gas cost (informational - Context may sponsor) */
|
|
533
|
+
estimatedGas?: string;
|
|
534
|
+
/** Link to contract on block explorer */
|
|
535
|
+
explorerUrl?: string;
|
|
536
|
+
};
|
|
537
|
+
/**
|
|
538
|
+
* Transaction Proposal
|
|
539
|
+
*
|
|
540
|
+
* Use this for protocols without proxy wallets (Uniswap, NFT mints, etc.).
|
|
541
|
+
*
|
|
542
|
+
* Note: May require network switching and gas fees.
|
|
543
|
+
*
|
|
544
|
+
* @example
|
|
545
|
+
* ```typescript
|
|
546
|
+
* return {
|
|
547
|
+
* structuredContent: {
|
|
548
|
+
* _meta: {
|
|
549
|
+
* handshakeAction: createTransactionProposal({
|
|
550
|
+
* chainId: 8453,
|
|
551
|
+
* to: "0x...",
|
|
552
|
+
* data: "0x...",
|
|
553
|
+
* meta: { description: "Swap 100 USDC for ETH", protocol: "Uniswap" }
|
|
554
|
+
* })
|
|
555
|
+
* }
|
|
556
|
+
* }
|
|
557
|
+
* };
|
|
558
|
+
* ```
|
|
559
|
+
*/
|
|
560
|
+
type TransactionProposal = {
|
|
561
|
+
_action: "transaction_proposal";
|
|
562
|
+
/** EVM chain ID (e.g., 137 for Polygon, 8453 for Base) */
|
|
563
|
+
chainId: number;
|
|
564
|
+
/** Target contract address */
|
|
565
|
+
to: `0x${string}`;
|
|
566
|
+
/** Encoded calldata */
|
|
567
|
+
data: `0x${string}`;
|
|
568
|
+
/** Wei to send (as string, default "0") */
|
|
569
|
+
value?: string;
|
|
570
|
+
/** UI metadata for the approval card */
|
|
571
|
+
meta?: TransactionProposalMeta;
|
|
572
|
+
};
|
|
573
|
+
type AuthRequiredMeta = {
|
|
574
|
+
/** Human-friendly service name */
|
|
575
|
+
displayName?: string;
|
|
576
|
+
/** Permissions being requested */
|
|
577
|
+
scopes?: string[];
|
|
578
|
+
/** Description of what access is needed */
|
|
579
|
+
description?: string;
|
|
580
|
+
/** Tool's icon URL */
|
|
581
|
+
iconUrl?: string;
|
|
582
|
+
/** How long authorization lasts */
|
|
583
|
+
expiresIn?: string;
|
|
584
|
+
};
|
|
585
|
+
/**
|
|
586
|
+
* Auth Required
|
|
587
|
+
*
|
|
588
|
+
* Use this when your tool needs the user to authenticate with an external service.
|
|
589
|
+
*
|
|
590
|
+
* @example
|
|
591
|
+
* ```typescript
|
|
592
|
+
* if (!hasUserToken(contextDid)) {
|
|
593
|
+
* return {
|
|
594
|
+
* structuredContent: {
|
|
595
|
+
* _meta: {
|
|
596
|
+
* handshakeAction: createAuthRequired({
|
|
597
|
+
* provider: "discord",
|
|
598
|
+
* authUrl: "https://your-server.com/oauth/discord",
|
|
599
|
+
* meta: { displayName: "Discord Bot", scopes: ["send_messages"] }
|
|
600
|
+
* })
|
|
601
|
+
* }
|
|
602
|
+
* }
|
|
603
|
+
* };
|
|
604
|
+
* }
|
|
605
|
+
* ```
|
|
606
|
+
*/
|
|
607
|
+
type AuthRequired = {
|
|
608
|
+
_action: "auth_required";
|
|
609
|
+
/** Service identifier (e.g., "discord", "slack") */
|
|
610
|
+
provider: string;
|
|
611
|
+
/** Your OAuth initiation endpoint (MUST be HTTPS) */
|
|
612
|
+
authUrl: string;
|
|
613
|
+
/** UI metadata for the auth card */
|
|
614
|
+
meta?: AuthRequiredMeta;
|
|
615
|
+
};
|
|
616
|
+
type HandshakeAction = SignatureRequest | TransactionProposal | AuthRequired;
|
|
617
|
+
declare function isHandshakeAction(value: unknown): value is HandshakeAction;
|
|
618
|
+
declare function isSignatureRequest(value: unknown): value is SignatureRequest;
|
|
619
|
+
declare function isTransactionProposal(value: unknown): value is TransactionProposal;
|
|
620
|
+
declare function isAuthRequired(value: unknown): value is AuthRequired;
|
|
621
|
+
/**
|
|
622
|
+
* Create a signature request response.
|
|
623
|
+
* Return this from your tool when you need the user to sign EIP-712 typed data.
|
|
624
|
+
*
|
|
625
|
+
* Use this for platforms with proxy wallets (Hyperliquid, Polymarket, dYdX).
|
|
626
|
+
* Benefits: No gas required, no network switching needed.
|
|
627
|
+
*/
|
|
628
|
+
declare function createSignatureRequest(params: Omit<SignatureRequest, "_action">): SignatureRequest;
|
|
629
|
+
/**
|
|
630
|
+
* Create a transaction proposal response.
|
|
631
|
+
* Return this from your tool when you need the user to sign a direct on-chain transaction.
|
|
632
|
+
*
|
|
633
|
+
* Use this for protocols that don't use proxy wallets (Uniswap, NFT mints, etc.).
|
|
634
|
+
* Note: May require network switching and gas.
|
|
635
|
+
*/
|
|
636
|
+
declare function createTransactionProposal(params: Omit<TransactionProposal, "_action">): TransactionProposal;
|
|
637
|
+
/**
|
|
638
|
+
* Create an auth required response.
|
|
639
|
+
* Return this from your tool when you need the user to authenticate via OAuth.
|
|
640
|
+
*/
|
|
641
|
+
declare function createAuthRequired(params: Omit<AuthRequired, "_action">): AuthRequired;
|
|
642
|
+
/**
|
|
643
|
+
* Wrap a handshake action in the proper MCP response format.
|
|
644
|
+
*
|
|
645
|
+
* MCP tools should return handshake actions in `_meta.handshakeAction` to prevent
|
|
646
|
+
* the MCP SDK from stripping unknown fields.
|
|
647
|
+
*
|
|
648
|
+
* @example
|
|
649
|
+
* ```typescript
|
|
650
|
+
* // In your tool handler:
|
|
651
|
+
* return wrapHandshakeResponse(createSignatureRequest({
|
|
652
|
+
* domain: { name: "Hyperliquid", version: "1", chainId: 42161 },
|
|
653
|
+
* types: { Order: [...] },
|
|
654
|
+
* primaryType: "Order",
|
|
655
|
+
* message: orderData,
|
|
656
|
+
* meta: { description: "Place order", protocol: "Hyperliquid" }
|
|
657
|
+
* }));
|
|
658
|
+
* ```
|
|
659
|
+
*/
|
|
660
|
+
declare function wrapHandshakeResponse(action: HandshakeAction): {
|
|
661
|
+
content: Array<{
|
|
662
|
+
type: "text";
|
|
663
|
+
text: string;
|
|
664
|
+
}>;
|
|
665
|
+
structuredContent: {
|
|
666
|
+
_meta: {
|
|
667
|
+
handshakeAction: HandshakeAction;
|
|
668
|
+
};
|
|
669
|
+
status: string;
|
|
670
|
+
message: string;
|
|
671
|
+
};
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
export { type AuthRequired, type AuthRequiredMeta, CONTEXT_REQUIREMENTS_KEY, type ContextMiddlewareRequest, type ContextRequirementType, type CreateContextMiddlewareOptions, type EIP712Domain, type EIP712TypeField, type ERC20Context, type ERC20TokenBalance, type HandshakeAction, type HandshakeMeta, type HyperliquidAccountSummary, type HyperliquidContext, type HyperliquidOrder, type HyperliquidPerpPosition, type HyperliquidSpotBalance, type PolymarketContext, type PolymarketOrder, type PolymarketPosition, type SignatureRequest, type ToolRequirements, type TransactionProposal, type TransactionProposalMeta, type UserContext, type VerifyRequestOptions, type WalletContext, createAuthRequired, createContextMiddleware, createSignatureRequest, createTransactionProposal, isAuthRequired, isHandshakeAction, isOpenMcpMethod, isProtectedMcpMethod, isSignatureRequest, isTransactionProposal, verifyContextRequest, wrapHandshakeResponse };
|
package/dist/index.d.ts
CHANGED
|
@@ -434,4 +434,241 @@ interface CreateContextMiddlewareOptions {
|
|
|
434
434
|
*/
|
|
435
435
|
declare function createContextMiddleware(options?: CreateContextMiddlewareOptions): (req: ContextRequest, res: ContextResponse, next: NextFunction) => Promise<void>;
|
|
436
436
|
|
|
437
|
-
|
|
437
|
+
/**
|
|
438
|
+
* Handshake Types for MCP Tool Developers
|
|
439
|
+
*
|
|
440
|
+
* Use these types when your tool needs to request user interaction
|
|
441
|
+
* before completing an action (signatures, transactions, OAuth).
|
|
442
|
+
*
|
|
443
|
+
* @see https://docs.ctxprotocol.com/guides/handshake-architecture
|
|
444
|
+
*
|
|
445
|
+
* ## Usage Pattern
|
|
446
|
+
*
|
|
447
|
+
* Tools return handshake actions in the `_meta.handshakeAction` field
|
|
448
|
+
* of their MCP response. The Context platform intercepts these and
|
|
449
|
+
* presents the appropriate UI to the user.
|
|
450
|
+
*
|
|
451
|
+
* ## Action Types
|
|
452
|
+
*
|
|
453
|
+
* - `signature_request`: For EIP-712 signatures (Hyperliquid, Polymarket, etc.)
|
|
454
|
+
* - `transaction_proposal`: For direct on-chain transactions (Uniswap, NFT mints)
|
|
455
|
+
* - `auth_required`: For OAuth flows (Discord, Twitter, etc.)
|
|
456
|
+
*/
|
|
457
|
+
type HandshakeMeta = {
|
|
458
|
+
/** Human-readable description of the action */
|
|
459
|
+
description: string;
|
|
460
|
+
/** Protocol name (e.g., "Hyperliquid", "Polymarket") */
|
|
461
|
+
protocol?: string;
|
|
462
|
+
/** Action verb (e.g., "Place Order", "Place Bid") */
|
|
463
|
+
action?: string;
|
|
464
|
+
/** Token symbol if relevant */
|
|
465
|
+
tokenSymbol?: string;
|
|
466
|
+
/** Human-readable token amount */
|
|
467
|
+
tokenAmount?: string;
|
|
468
|
+
/** UI warning level */
|
|
469
|
+
warningLevel?: "info" | "caution" | "danger";
|
|
470
|
+
};
|
|
471
|
+
type EIP712Domain = {
|
|
472
|
+
/** Domain name (e.g., "Hyperliquid", "ClobAuthDomain") */
|
|
473
|
+
name: string;
|
|
474
|
+
/** Domain version */
|
|
475
|
+
version: string;
|
|
476
|
+
/** Chain ID (informational - signing is chain-agnostic) */
|
|
477
|
+
chainId: number;
|
|
478
|
+
/** Optional verifying contract address */
|
|
479
|
+
verifyingContract?: `0x${string}`;
|
|
480
|
+
};
|
|
481
|
+
type EIP712TypeField = {
|
|
482
|
+
name: string;
|
|
483
|
+
type: string;
|
|
484
|
+
};
|
|
485
|
+
/**
|
|
486
|
+
* Signature Request
|
|
487
|
+
*
|
|
488
|
+
* Use this for platforms with proxy wallets (Hyperliquid, Polymarket, dYdX).
|
|
489
|
+
*
|
|
490
|
+
* Benefits:
|
|
491
|
+
* - No gas required (user signs a message, not a transaction)
|
|
492
|
+
* - No network switching needed (signing is chain-agnostic)
|
|
493
|
+
* - Works with Privy embedded wallets on any chain
|
|
494
|
+
*
|
|
495
|
+
* @example
|
|
496
|
+
* ```typescript
|
|
497
|
+
* return {
|
|
498
|
+
* structuredContent: {
|
|
499
|
+
* _meta: {
|
|
500
|
+
* handshakeAction: createSignatureRequest({
|
|
501
|
+
* domain: { name: "Hyperliquid", version: "1", chainId: 42161 },
|
|
502
|
+
* types: { Order: [...] },
|
|
503
|
+
* primaryType: "Order",
|
|
504
|
+
* message: { asset: 4, isBuy: true, ... },
|
|
505
|
+
* meta: { description: "Place Long ETH order", protocol: "Hyperliquid" }
|
|
506
|
+
* })
|
|
507
|
+
* }
|
|
508
|
+
* }
|
|
509
|
+
* };
|
|
510
|
+
* ```
|
|
511
|
+
*/
|
|
512
|
+
type SignatureRequest = {
|
|
513
|
+
_action: "signature_request";
|
|
514
|
+
/** EIP-712 domain separator */
|
|
515
|
+
domain: EIP712Domain;
|
|
516
|
+
/** EIP-712 type definitions */
|
|
517
|
+
types: Record<string, EIP712TypeField[]>;
|
|
518
|
+
/** The primary type being signed */
|
|
519
|
+
primaryType: string;
|
|
520
|
+
/** The message data to sign */
|
|
521
|
+
message: Record<string, unknown>;
|
|
522
|
+
/** UI metadata for the approval card */
|
|
523
|
+
meta?: HandshakeMeta;
|
|
524
|
+
/**
|
|
525
|
+
* Optional: Tool name to call with the signature result.
|
|
526
|
+
* If provided, the platform will call this tool with { signature, originalParams }
|
|
527
|
+
* after the user signs.
|
|
528
|
+
*/
|
|
529
|
+
callbackToolName?: string;
|
|
530
|
+
};
|
|
531
|
+
type TransactionProposalMeta = HandshakeMeta & {
|
|
532
|
+
/** Estimated gas cost (informational - Context may sponsor) */
|
|
533
|
+
estimatedGas?: string;
|
|
534
|
+
/** Link to contract on block explorer */
|
|
535
|
+
explorerUrl?: string;
|
|
536
|
+
};
|
|
537
|
+
/**
|
|
538
|
+
* Transaction Proposal
|
|
539
|
+
*
|
|
540
|
+
* Use this for protocols without proxy wallets (Uniswap, NFT mints, etc.).
|
|
541
|
+
*
|
|
542
|
+
* Note: May require network switching and gas fees.
|
|
543
|
+
*
|
|
544
|
+
* @example
|
|
545
|
+
* ```typescript
|
|
546
|
+
* return {
|
|
547
|
+
* structuredContent: {
|
|
548
|
+
* _meta: {
|
|
549
|
+
* handshakeAction: createTransactionProposal({
|
|
550
|
+
* chainId: 8453,
|
|
551
|
+
* to: "0x...",
|
|
552
|
+
* data: "0x...",
|
|
553
|
+
* meta: { description: "Swap 100 USDC for ETH", protocol: "Uniswap" }
|
|
554
|
+
* })
|
|
555
|
+
* }
|
|
556
|
+
* }
|
|
557
|
+
* };
|
|
558
|
+
* ```
|
|
559
|
+
*/
|
|
560
|
+
type TransactionProposal = {
|
|
561
|
+
_action: "transaction_proposal";
|
|
562
|
+
/** EVM chain ID (e.g., 137 for Polygon, 8453 for Base) */
|
|
563
|
+
chainId: number;
|
|
564
|
+
/** Target contract address */
|
|
565
|
+
to: `0x${string}`;
|
|
566
|
+
/** Encoded calldata */
|
|
567
|
+
data: `0x${string}`;
|
|
568
|
+
/** Wei to send (as string, default "0") */
|
|
569
|
+
value?: string;
|
|
570
|
+
/** UI metadata for the approval card */
|
|
571
|
+
meta?: TransactionProposalMeta;
|
|
572
|
+
};
|
|
573
|
+
type AuthRequiredMeta = {
|
|
574
|
+
/** Human-friendly service name */
|
|
575
|
+
displayName?: string;
|
|
576
|
+
/** Permissions being requested */
|
|
577
|
+
scopes?: string[];
|
|
578
|
+
/** Description of what access is needed */
|
|
579
|
+
description?: string;
|
|
580
|
+
/** Tool's icon URL */
|
|
581
|
+
iconUrl?: string;
|
|
582
|
+
/** How long authorization lasts */
|
|
583
|
+
expiresIn?: string;
|
|
584
|
+
};
|
|
585
|
+
/**
|
|
586
|
+
* Auth Required
|
|
587
|
+
*
|
|
588
|
+
* Use this when your tool needs the user to authenticate with an external service.
|
|
589
|
+
*
|
|
590
|
+
* @example
|
|
591
|
+
* ```typescript
|
|
592
|
+
* if (!hasUserToken(contextDid)) {
|
|
593
|
+
* return {
|
|
594
|
+
* structuredContent: {
|
|
595
|
+
* _meta: {
|
|
596
|
+
* handshakeAction: createAuthRequired({
|
|
597
|
+
* provider: "discord",
|
|
598
|
+
* authUrl: "https://your-server.com/oauth/discord",
|
|
599
|
+
* meta: { displayName: "Discord Bot", scopes: ["send_messages"] }
|
|
600
|
+
* })
|
|
601
|
+
* }
|
|
602
|
+
* }
|
|
603
|
+
* };
|
|
604
|
+
* }
|
|
605
|
+
* ```
|
|
606
|
+
*/
|
|
607
|
+
type AuthRequired = {
|
|
608
|
+
_action: "auth_required";
|
|
609
|
+
/** Service identifier (e.g., "discord", "slack") */
|
|
610
|
+
provider: string;
|
|
611
|
+
/** Your OAuth initiation endpoint (MUST be HTTPS) */
|
|
612
|
+
authUrl: string;
|
|
613
|
+
/** UI metadata for the auth card */
|
|
614
|
+
meta?: AuthRequiredMeta;
|
|
615
|
+
};
|
|
616
|
+
type HandshakeAction = SignatureRequest | TransactionProposal | AuthRequired;
|
|
617
|
+
declare function isHandshakeAction(value: unknown): value is HandshakeAction;
|
|
618
|
+
declare function isSignatureRequest(value: unknown): value is SignatureRequest;
|
|
619
|
+
declare function isTransactionProposal(value: unknown): value is TransactionProposal;
|
|
620
|
+
declare function isAuthRequired(value: unknown): value is AuthRequired;
|
|
621
|
+
/**
|
|
622
|
+
* Create a signature request response.
|
|
623
|
+
* Return this from your tool when you need the user to sign EIP-712 typed data.
|
|
624
|
+
*
|
|
625
|
+
* Use this for platforms with proxy wallets (Hyperliquid, Polymarket, dYdX).
|
|
626
|
+
* Benefits: No gas required, no network switching needed.
|
|
627
|
+
*/
|
|
628
|
+
declare function createSignatureRequest(params: Omit<SignatureRequest, "_action">): SignatureRequest;
|
|
629
|
+
/**
|
|
630
|
+
* Create a transaction proposal response.
|
|
631
|
+
* Return this from your tool when you need the user to sign a direct on-chain transaction.
|
|
632
|
+
*
|
|
633
|
+
* Use this for protocols that don't use proxy wallets (Uniswap, NFT mints, etc.).
|
|
634
|
+
* Note: May require network switching and gas.
|
|
635
|
+
*/
|
|
636
|
+
declare function createTransactionProposal(params: Omit<TransactionProposal, "_action">): TransactionProposal;
|
|
637
|
+
/**
|
|
638
|
+
* Create an auth required response.
|
|
639
|
+
* Return this from your tool when you need the user to authenticate via OAuth.
|
|
640
|
+
*/
|
|
641
|
+
declare function createAuthRequired(params: Omit<AuthRequired, "_action">): AuthRequired;
|
|
642
|
+
/**
|
|
643
|
+
* Wrap a handshake action in the proper MCP response format.
|
|
644
|
+
*
|
|
645
|
+
* MCP tools should return handshake actions in `_meta.handshakeAction` to prevent
|
|
646
|
+
* the MCP SDK from stripping unknown fields.
|
|
647
|
+
*
|
|
648
|
+
* @example
|
|
649
|
+
* ```typescript
|
|
650
|
+
* // In your tool handler:
|
|
651
|
+
* return wrapHandshakeResponse(createSignatureRequest({
|
|
652
|
+
* domain: { name: "Hyperliquid", version: "1", chainId: 42161 },
|
|
653
|
+
* types: { Order: [...] },
|
|
654
|
+
* primaryType: "Order",
|
|
655
|
+
* message: orderData,
|
|
656
|
+
* meta: { description: "Place order", protocol: "Hyperliquid" }
|
|
657
|
+
* }));
|
|
658
|
+
* ```
|
|
659
|
+
*/
|
|
660
|
+
declare function wrapHandshakeResponse(action: HandshakeAction): {
|
|
661
|
+
content: Array<{
|
|
662
|
+
type: "text";
|
|
663
|
+
text: string;
|
|
664
|
+
}>;
|
|
665
|
+
structuredContent: {
|
|
666
|
+
_meta: {
|
|
667
|
+
handshakeAction: HandshakeAction;
|
|
668
|
+
};
|
|
669
|
+
status: string;
|
|
670
|
+
message: string;
|
|
671
|
+
};
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
export { type AuthRequired, type AuthRequiredMeta, CONTEXT_REQUIREMENTS_KEY, type ContextMiddlewareRequest, type ContextRequirementType, type CreateContextMiddlewareOptions, type EIP712Domain, type EIP712TypeField, type ERC20Context, type ERC20TokenBalance, type HandshakeAction, type HandshakeMeta, type HyperliquidAccountSummary, type HyperliquidContext, type HyperliquidOrder, type HyperliquidPerpPosition, type HyperliquidSpotBalance, type PolymarketContext, type PolymarketOrder, type PolymarketPosition, type SignatureRequest, type ToolRequirements, type TransactionProposal, type TransactionProposalMeta, type UserContext, type VerifyRequestOptions, type WalletContext, createAuthRequired, createContextMiddleware, createSignatureRequest, createTransactionProposal, isAuthRequired, isHandshakeAction, isOpenMcpMethod, isProtectedMcpMethod, isSignatureRequest, isTransactionProposal, verifyContextRequest, wrapHandshakeResponse };
|
package/dist/index.js
CHANGED
|
@@ -262,6 +262,56 @@ function createContextMiddleware(options = {}) {
|
|
|
262
262
|
};
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
|
|
265
|
+
// src/handshake/types.ts
|
|
266
|
+
function isHandshakeAction(value) {
|
|
267
|
+
return typeof value === "object" && value !== null && "_action" in value && (value._action === "signature_request" || value._action === "transaction_proposal" || value._action === "auth_required");
|
|
268
|
+
}
|
|
269
|
+
function isSignatureRequest(value) {
|
|
270
|
+
return isHandshakeAction(value) && value._action === "signature_request";
|
|
271
|
+
}
|
|
272
|
+
function isTransactionProposal(value) {
|
|
273
|
+
return isHandshakeAction(value) && value._action === "transaction_proposal";
|
|
274
|
+
}
|
|
275
|
+
function isAuthRequired(value) {
|
|
276
|
+
return isHandshakeAction(value) && value._action === "auth_required";
|
|
277
|
+
}
|
|
278
|
+
function createSignatureRequest(params) {
|
|
279
|
+
return {
|
|
280
|
+
_action: "signature_request",
|
|
281
|
+
...params
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function createTransactionProposal(params) {
|
|
285
|
+
return {
|
|
286
|
+
_action: "transaction_proposal",
|
|
287
|
+
...params
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function createAuthRequired(params) {
|
|
291
|
+
return {
|
|
292
|
+
_action: "auth_required",
|
|
293
|
+
...params
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function wrapHandshakeResponse(action) {
|
|
297
|
+
const actionType = action._action.replace("_", " ");
|
|
298
|
+
return {
|
|
299
|
+
content: [
|
|
300
|
+
{
|
|
301
|
+
type: "text",
|
|
302
|
+
text: `Handshake required: ${actionType}. Please approve in the Context app.`
|
|
303
|
+
}
|
|
304
|
+
],
|
|
305
|
+
structuredContent: {
|
|
306
|
+
_meta: {
|
|
307
|
+
handshakeAction: action
|
|
308
|
+
},
|
|
309
|
+
status: "handshake_required",
|
|
310
|
+
message: action.meta?.description ?? `${actionType} required`
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export { CONTEXT_REQUIREMENTS_KEY, ContextClient, ContextError, Discovery, Tools, createAuthRequired, createContextMiddleware, createSignatureRequest, createTransactionProposal, isAuthRequired, isHandshakeAction, isOpenMcpMethod, isProtectedMcpMethod, isSignatureRequest, isTransactionProposal, verifyContextRequest, wrapHandshakeResponse };
|
|
266
316
|
//# sourceMappingURL=index.js.map
|
|
267
317
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client/types.ts","../src/client/resources/discovery.ts","../src/client/resources/tools.ts","../src/client/client.ts","../src/context/index.ts","../src/auth/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Configuration options for initializing the ContextClient\n */\nexport interface ContextClientOptions {\n /**\n * Your Context Protocol API key\n * @example \"sk_live_abc123...\"\n */\n apiKey: string;\n\n /**\n * Base URL for the Context Protocol API\n * @default \"https://ctxprotocol.com\"\n */\n baseUrl?: string;\n}\n\n/**\n * An individual MCP tool exposed by a tool listing\n */\nexport interface McpTool {\n /** Name of the MCP tool method */\n name: string;\n\n /** Description of what this method does */\n description: string;\n\n /**\n * JSON Schema for the input arguments this tool accepts.\n * Used by LLMs to generate correct arguments.\n */\n inputSchema?: Record<string, unknown>;\n\n /**\n * JSON Schema for the output this tool returns.\n * Used by LLMs to understand the response structure.\n */\n outputSchema?: Record<string, unknown>;\n}\n\n/**\n * Represents a tool available on the Context Protocol marketplace\n */\nexport interface Tool {\n /** Unique identifier for the tool (UUID) */\n id: string;\n\n /** Human-readable name of the tool */\n name: string;\n\n /** Description of what the tool does */\n description: string;\n\n /** Price per execution in USDC */\n price: string;\n\n /** Tool category (e.g., \"defi\", \"nft\") */\n category?: string;\n\n /** Whether the tool is verified by Context Protocol */\n isVerified?: boolean;\n\n /**\n * Available MCP tool methods\n * Use items from this array as `toolName` when executing\n */\n mcpTools?: McpTool[];\n\n /** Creation timestamp */\n createdAt?: string;\n\n /** Last update timestamp */\n updatedAt?: string;\n}\n\n/**\n * Response from the tools search endpoint\n */\nexport interface SearchResponse {\n /** Array of matching tools */\n tools: Tool[];\n\n /** The search query that was used */\n query: string;\n\n /** Total number of results */\n count: number;\n}\n\n/**\n * Options for searching tools\n */\nexport interface SearchOptions {\n /** Search query (semantic search) */\n query?: string;\n\n /** Maximum number of results (1-50, default 10) */\n limit?: number;\n}\n\n/**\n * Options for executing a tool\n */\nexport interface ExecuteOptions {\n /** The UUID of the tool to execute (from search results) */\n toolId: string;\n\n /** The specific MCP tool name to call (from tool's mcpTools array) */\n toolName: string;\n\n /** Arguments to pass to the tool */\n args?: Record<string, unknown>;\n}\n\n/**\n * Successful execution response from the API\n */\nexport interface ExecuteApiSuccessResponse {\n success: true;\n\n /** The result data from the tool execution */\n result: unknown;\n\n /** Information about the executed tool */\n tool: {\n id: string;\n name: string;\n };\n\n /** Execution duration in milliseconds */\n durationMs: number;\n}\n\n/**\n * Error response from the API\n */\nexport interface ExecuteApiErrorResponse {\n /** Human-readable error message */\n error: string;\n\n /** Error code for programmatic handling */\n code?: ContextErrorCode;\n\n /** URL to help resolve the issue */\n helpUrl?: string;\n}\n\n/**\n * Raw API response from the execute endpoint\n */\nexport type ExecuteApiResponse = ExecuteApiSuccessResponse | ExecuteApiErrorResponse;\n\n/**\n * The resolved result returned to the user after SDK processing\n */\nexport interface ExecutionResult<T = unknown> {\n /** The data returned by the tool */\n result: T;\n\n /** Information about the executed tool */\n tool: {\n id: string;\n name: string;\n };\n\n /** Execution duration in milliseconds */\n durationMs: number;\n}\n\n/**\n * Specific error codes returned by the Context Protocol API\n */\nexport type ContextErrorCode =\n | \"unauthorized\"\n | \"no_wallet\"\n | \"insufficient_allowance\"\n | \"payment_failed\"\n | \"execution_failed\";\n\n/**\n * Error thrown by the Context Protocol client\n */\nexport class ContextError extends Error {\n constructor(\n message: string,\n public readonly code?: ContextErrorCode | string,\n public readonly statusCode?: number,\n public readonly helpUrl?: string\n ) {\n super(message);\n this.name = \"ContextError\";\n }\n}\n","import type { Tool, SearchResponse } from \"../types.js\";\nimport type { ContextClient } from \"../client.js\";\n\n/**\n * Discovery resource for searching and finding tools on the Context Protocol marketplace\n */\nexport class Discovery {\n constructor(private client: ContextClient) {}\n\n /**\n * Search for tools matching a query string\n *\n * @param query - The search query (e.g., \"gas prices\", \"nft metadata\")\n * @param limit - Maximum number of results (1-50, default 10)\n * @returns Array of matching tools\n *\n * @example\n * ```typescript\n * const tools = await client.discovery.search(\"gas prices\");\n * console.log(tools[0].name); // \"Gas Price Oracle\"\n * console.log(tools[0].mcpTools); // Available methods\n * ```\n */\n async search(query: string, limit?: number): Promise<Tool[]> {\n const params = new URLSearchParams();\n\n if (query) {\n params.set(\"q\", query);\n }\n\n if (limit !== undefined) {\n params.set(\"limit\", String(limit));\n }\n\n const queryString = params.toString();\n const endpoint = `/api/v1/tools/search${queryString ? `?${queryString}` : \"\"}`;\n\n const response = await this.client.fetch<SearchResponse>(endpoint);\n\n return response.tools;\n }\n\n /**\n * Get featured/popular tools (empty query search)\n *\n * @param limit - Maximum number of results (1-50, default 10)\n * @returns Array of featured tools\n *\n * @example\n * ```typescript\n * const featured = await client.discovery.getFeatured(5);\n * ```\n */\n async getFeatured(limit?: number): Promise<Tool[]> {\n return this.search(\"\", limit);\n }\n}\n","import type {\n ExecuteOptions,\n ExecuteApiResponse,\n ExecutionResult,\n} from \"../types.js\";\nimport { ContextError } from \"../types.js\";\nimport type { ContextClient } from \"../client.js\";\n\n/**\n * Tools resource for executing tools on the Context Protocol marketplace\n */\nexport class Tools {\n constructor(private client: ContextClient) {}\n\n /**\n * Execute a tool with the provided arguments\n *\n * @param options - Execution options\n * @param options.toolId - The UUID of the tool (from search results)\n * @param options.toolName - The specific MCP tool method to call (from tool's mcpTools array)\n * @param options.args - Arguments to pass to the tool\n * @returns The execution result with the tool's output data\n *\n * @throws {ContextError} With code `no_wallet` if wallet not set up\n * @throws {ContextError} With code `insufficient_allowance` if Auto Pay not enabled\n * @throws {ContextError} With code `payment_failed` if on-chain payment fails\n * @throws {ContextError} With code `execution_failed` if tool execution fails\n *\n * @example\n * ```typescript\n * // First, search for a tool\n * const tools = await client.discovery.search(\"gas prices\");\n * const tool = tools[0];\n *\n * // Execute a specific method from the tool's mcpTools\n * const result = await client.tools.execute({\n * toolId: tool.id,\n * toolName: tool.mcpTools[0].name, // e.g., \"get_gas_prices\"\n * args: { chainId: 1 }\n * });\n *\n * console.log(result.result); // The tool's output\n * console.log(result.durationMs); // Execution time\n * ```\n */\n async execute<T = unknown>(options: ExecuteOptions): Promise<ExecutionResult<T>> {\n const { toolId, toolName, args } = options;\n\n const response = await this.client.fetch<ExecuteApiResponse>(\n \"/api/v1/tools/execute\",\n {\n method: \"POST\",\n body: JSON.stringify({ toolId, toolName, args }),\n }\n );\n\n // Handle error response\n if (\"error\" in response) {\n throw new ContextError(\n response.error,\n response.code,\n 400,\n response.helpUrl\n );\n }\n\n // Handle success response\n if (response.success) {\n return {\n result: response.result as T,\n tool: response.tool,\n durationMs: response.durationMs,\n };\n }\n\n // Fallback - shouldn't reach here with valid API responses\n throw new ContextError(\"Unexpected response format from API\");\n }\n}\n","import type { ContextClientOptions } from \"./types.js\";\nimport { ContextError } from \"./types.js\";\nimport { Discovery } from \"./resources/discovery.js\";\nimport { Tools } from \"./resources/tools.js\";\n\n/**\n * The official TypeScript client for the Context Protocol.\n *\n * Use this client to discover and execute AI tools programmatically.\n *\n * @example\n * ```typescript\n * import { ContextClient } from \"@contextprotocol/client\";\n *\n * const client = new ContextClient({\n * apiKey: \"sk_live_...\"\n * });\n *\n * // Discover tools\n * const tools = await client.discovery.search(\"gas prices\");\n *\n * // Execute a tool method\n * const result = await client.tools.execute({\n * toolId: tools[0].id,\n * toolName: tools[0].mcpTools[0].name,\n * args: { chainId: 1 }\n * });\n * ```\n */\nexport class ContextClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n /**\n * Discovery resource for searching tools\n */\n public readonly discovery: Discovery;\n\n /**\n * Tools resource for executing tools\n */\n public readonly tools: Tools;\n\n /**\n * Creates a new Context Protocol client\n *\n * @param options - Client configuration options\n * @param options.apiKey - Your Context Protocol API key (format: sk_live_...)\n * @param options.baseUrl - Optional base URL override (defaults to https://ctxprotocol.com)\n */\n constructor(options: ContextClientOptions) {\n if (!options.apiKey) {\n throw new ContextError(\"API key is required\");\n }\n\n this.apiKey = options.apiKey;\n this.baseUrl = (options.baseUrl ?? \"https://ctxprotocol.com\").replace(/\\/$/, \"\");\n\n // Initialize resources\n this.discovery = new Discovery(this);\n this.tools = new Tools(this);\n }\n\n /**\n * Internal method for making authenticated HTTP requests\n * All requests include the Authorization header with the API key\n *\n * @internal\n */\n async fetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`;\n\n const response = await fetch(url, {\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n let errorMessage = `HTTP ${response.status}: ${response.statusText}`;\n let errorCode: string | undefined;\n let helpUrl: string | undefined;\n\n try {\n const errorBody = await response.json();\n if (errorBody.error) {\n errorMessage = errorBody.error;\n errorCode = errorBody.code;\n helpUrl = errorBody.helpUrl;\n }\n } catch {\n // Use default error message if JSON parsing fails\n }\n\n throw new ContextError(errorMessage, errorCode, response.status, helpUrl);\n }\n\n return response.json() as Promise<T>;\n }\n}\n","/**\n * Context types for portfolio and protocol data injection.\n *\n * These types allow MCP tools to receive personalized user context\n * (wallet addresses, positions, balances) for analysis.\n *\n * =============================================================================\n * DECLARING CONTEXT REQUIREMENTS\n * =============================================================================\n *\n * Since the MCP protocol only transmits standard fields (name, description,\n * inputSchema, outputSchema), context requirements MUST be embedded in the\n * inputSchema using the \"x-context-requirements\" JSON Schema extension.\n *\n * @example\n * ```typescript\n * import { CONTEXT_REQUIREMENTS_KEY, type ContextRequirementType } from \"@ctxprotocol/sdk\";\n * import type { HyperliquidContext } from \"@ctxprotocol/sdk\";\n *\n * const tool = {\n * name: \"analyze_my_positions\",\n * inputSchema: {\n * type: \"object\",\n * [CONTEXT_REQUIREMENTS_KEY]: [\"hyperliquid\"] as ContextRequirementType[],\n * properties: {\n * portfolio: { type: \"object\" }\n * },\n * required: [\"portfolio\"]\n * }\n * };\n *\n * // Your handler receives the injected context:\n * function handleAnalyzeMyPositions(args: { portfolio: HyperliquidContext }) {\n * const { perpPositions, accountSummary } = args.portfolio;\n * // ... analyze and return insights\n * }\n * ```\n *\n * @packageDocumentation\n */\n\n// Wallet context types\nexport * from \"./wallet.js\";\n\n// Protocol-specific context types\nexport * from \"./polymarket.js\";\nexport * from \"./hyperliquid.js\";\n\n// Re-import for composite type\nimport type { WalletContext, ERC20Context } from \"./wallet.js\";\nimport type { PolymarketContext } from \"./polymarket.js\";\nimport type { HyperliquidContext } from \"./hyperliquid.js\";\n\n// ============================================================================\n// CONTEXT REQUIREMENTS\n//\n// MCP tools that need user portfolio data MUST declare this in inputSchema.\n// The MCP protocol only transmits standard fields (name, description,\n// inputSchema, outputSchema). Custom fields get stripped by the MCP SDK.\n// ============================================================================\n\n/**\n * JSON Schema extension key for declaring context requirements.\n *\n * WHY THIS APPROACH?\n * - MCP protocol only transmits: name, description, inputSchema, outputSchema\n * - Custom fields like `requirements` get stripped by MCP SDK during transport\n * - JSON Schema allows custom \"x-\" prefixed extension properties\n * - inputSchema is preserved end-to-end through MCP transport\n *\n * @example\n * ```typescript\n * import { CONTEXT_REQUIREMENTS_KEY } from \"@ctxprotocol/sdk\";\n *\n * const tool = {\n * name: \"analyze_my_positions\",\n * inputSchema: {\n * type: \"object\",\n * [CONTEXT_REQUIREMENTS_KEY]: [\"hyperliquid\"],\n * properties: { portfolio: { type: \"object\" } },\n * required: [\"portfolio\"]\n * }\n * };\n * ```\n */\nexport const CONTEXT_REQUIREMENTS_KEY = \"x-context-requirements\" as const;\n\n/**\n * Context requirement types supported by the Context marketplace.\n * Maps to protocol-specific context builders on the platform.\n *\n * @example\n * ```typescript\n * inputSchema: {\n * type: \"object\",\n * \"x-context-requirements\": [\"hyperliquid\"] as ContextRequirementType[],\n * properties: { portfolio: { type: \"object\" } },\n * required: [\"portfolio\"]\n * }\n * ```\n */\nexport type ContextRequirementType = \"polymarket\" | \"hyperliquid\" | \"wallet\";\n\n/**\n * @deprecated The `requirements` field at tool level gets stripped by MCP SDK.\n * Use `x-context-requirements` inside `inputSchema` instead.\n *\n * @example\n * ```typescript\n * // ❌ OLD (doesn't work - stripped by MCP SDK)\n * { requirements: { context: [\"hyperliquid\"] } }\n *\n * // ✅ NEW (works - preserved through MCP transport)\n * { inputSchema: { \"x-context-requirements\": [\"hyperliquid\"], ... } }\n * ```\n */\nexport interface ToolRequirements {\n /**\n * @deprecated Use `x-context-requirements` in inputSchema instead.\n */\n context?: ContextRequirementType[];\n}\n\n/**\n * Composite context for tools that need multiple data sources.\n *\n * This is the unified structure that can be passed to MCP tools\n * to provide comprehensive user context.\n */\nexport interface UserContext {\n /** Base wallet information */\n wallet?: WalletContext;\n /** ERC20 token holdings */\n erc20?: ERC20Context;\n /** Polymarket positions and orders */\n polymarket?: PolymarketContext;\n /** Hyperliquid perpetual positions and account data */\n hyperliquid?: HyperliquidContext;\n // Future protocols:\n // aave?: AaveContext;\n}\n","import { jwtVerify, importSPKI, type JWTPayload } from \"jose\";\nimport { ContextError } from \"../client/types.js\";\n\n// ============================================================================\n// Express-compatible types (avoid requiring express as a dependency)\n// ============================================================================\n\ninterface ContextRequest {\n headers: {\n authorization?: string;\n [key: string]: string | string[] | undefined;\n };\n body?: {\n method?: string;\n [key: string]: unknown;\n };\n context?: JWTPayload;\n}\n\ninterface ContextResponse {\n status(code: number): ContextResponse;\n json(data: unknown): void;\n}\n\ntype NextFunction = (error?: unknown) => void;\n\n/**\n * Extended Request object with verified Context Protocol JWT payload.\n *\n * After `createContextMiddleware()` runs successfully on a protected method,\n * the `context` property contains the decoded JWT claims.\n */\nexport interface ContextMiddlewareRequest extends ContextRequest {\n /** The verified JWT payload from Context Protocol (available after auth) */\n context?: JWTPayload;\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n// The Context Protocol Public Key\n// In a real scenario, this might be fetched from a well-known URL or passed in config.\n// For now, we hardcode the Official Platform Public Key.\n// Official Context Protocol Platform Public Key (RS256)\nconst CONTEXT_PLATFORM_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs9YOgdpkmVQ5aoNovjsu\nchJdV54OT7dUdbVXz914a7Px8EwnpDqhsvG7WO8xL8sj2Rn6ueAJBk+04Hy/P/UN\nRJyp23XL5TsGmb4rbfg0ii0MiL2nbVXuqvAe3JSM2BOFZR5bpwIVIaa8aonfamUy\nVXGc7OosF90ThdKjm9cXlVM+kV6IgSWc1502X7M3abQqRcTU/rluVXnky0eiWDQa\nlfOKbr7w0u72dZjiZPwnNDsX6PEEgvfmoautTFYTQgnZjDzq8UimTcv3KF+hJ5Ep\nweipe6amt9lzQzi8WXaFKpOXHQs//WDlUytz/Hl8pvd5craZKzo6Kyrg1Vfan7H3\nTQIDAQAB\n-----END PUBLIC KEY-----`;\n\n/**\n * MCP methods that require authentication.\n * - tools/call: Executes tool logic, may cost money\n * - resources/read: Reads potentially sensitive data\n * - prompts/get: Gets prompt content\n */\nconst PROTECTED_MCP_METHODS = new Set([\n \"tools/call\",\n // Uncomment these if you want to protect resource/prompt access:\n // \"resources/read\",\n // \"prompts/get\",\n]);\n\n/**\n * MCP methods that are always open (no auth required).\n * These are discovery/listing operations that return metadata only.\n */\nconst OPEN_MCP_METHODS = new Set([\n \"initialize\",\n \"tools/list\",\n \"resources/list\",\n \"prompts/list\",\n \"ping\",\n \"notifications/initialized\",\n]);\n\n// ============================================================================\n// Method Classification\n// ============================================================================\n\n/**\n * Determines if a given MCP method requires authentication.\n *\n * Discovery methods (tools/list, resources/list, etc.) are open.\n * Execution methods (tools/call) require authentication.\n *\n * @param method The MCP JSON-RPC method (e.g., \"tools/list\", \"tools/call\")\n * @returns true if the method requires authentication\n *\n * @example\n * ```typescript\n * if (isProtectedMcpMethod(body.method)) {\n * await verifyContextRequest({ authorizationHeader: req.headers.authorization });\n * }\n * ```\n */\nexport function isProtectedMcpMethod(method: string): boolean {\n return PROTECTED_MCP_METHODS.has(method);\n}\n\n/**\n * Determines if a given MCP method is explicitly open (no auth).\n *\n * @param method The MCP JSON-RPC method\n * @returns true if the method is known to be open\n */\nexport function isOpenMcpMethod(method: string): boolean {\n return OPEN_MCP_METHODS.has(method);\n}\n\n// ============================================================================\n// Request Verification\n// ============================================================================\n\nexport interface VerifyRequestOptions {\n /** The full Authorization header string (e.g. \"Bearer eyJ...\") */\n authorizationHeader?: string;\n /** Expected Audience (your tool URL) for stricter validation */\n audience?: string;\n}\n\n/**\n * Verifies that an incoming request originated from the Context Protocol Platform.\n *\n * @param options Contains the Authorization header\n * @returns The decoded payload if valid\n * @throws ContextError if invalid\n */\nexport async function verifyContextRequest(options: VerifyRequestOptions) {\n const { authorizationHeader, audience } = options;\n\n if (!authorizationHeader || !authorizationHeader.startsWith(\"Bearer \")) {\n throw new ContextError(\n \"Missing or invalid Authorization header\",\n \"unauthorized\",\n 401\n );\n }\n\n const token = authorizationHeader.split(\" \")[1];\n\n try {\n const publicKey = await importSPKI(CONTEXT_PLATFORM_PUBLIC_KEY_PEM, \"RS256\");\n\n const { payload } = await jwtVerify(token, publicKey, {\n issuer: \"https://ctxprotocol.com\",\n audience: audience,\n });\n\n return payload;\n } catch (error) {\n throw new ContextError(\n \"Invalid Context Protocol signature\",\n \"unauthorized\",\n 401\n );\n }\n}\n\n// ============================================================================\n// Easy-Mode Middleware\n// ============================================================================\n\nexport interface CreateContextMiddlewareOptions {\n /** Expected Audience (your tool URL) for stricter validation */\n audience?: string;\n}\n\n/**\n * Creates an Express/Connect-compatible middleware that secures your MCP endpoint.\n *\n * This is the \"1 line of code\" solution to secure your MCP server.\n * It automatically:\n * - Allows discovery methods (tools/list, initialize) without authentication\n * - Requires and verifies JWT for execution methods (tools/call)\n * - Attaches the verified payload to `req.context` for downstream use\n *\n * @param options Optional configuration\n * @returns Express-compatible middleware function\n *\n * @example\n * ```typescript\n * import express from \"express\";\n * import { createContextMiddleware } from \"@ctxprotocol/sdk\";\n *\n * const app = express();\n * app.use(express.json());\n *\n * // 1 line to secure your endpoint\n * app.use(\"/mcp\", createContextMiddleware());\n *\n * app.post(\"/mcp\", (req, res) => {\n * // req.context contains verified JWT payload (on protected methods)\n * // Handle MCP request...\n * });\n * ```\n */\nexport function createContextMiddleware(options: CreateContextMiddlewareOptions = {}) {\n return async function contextMiddleware(\n req: ContextRequest,\n res: ContextResponse,\n next: NextFunction\n ): Promise<void> {\n const method = req.body?.method as string | undefined;\n\n // Allow discovery methods without authentication\n // Discovery methods (tools/list, initialize, etc.) are open by design\n if (!method || !isProtectedMcpMethod(method)) {\n return next();\n }\n\n // Protected method - require authentication\n try {\n const payload = await verifyContextRequest({\n authorizationHeader: req.headers.authorization,\n audience: options.audience,\n });\n\n // Attach verified payload to request for downstream handlers\n req.context = payload;\n next();\n } catch (error) {\n const statusCode = error instanceof ContextError ? error.statusCode || 401 : 401;\n res.status(statusCode).json({ error: \"Unauthorized\" });\n }\n };\n}\n\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/client/types.ts","../src/client/resources/discovery.ts","../src/client/resources/tools.ts","../src/client/client.ts","../src/context/index.ts","../src/auth/index.ts","../src/handshake/types.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Configuration options for initializing the ContextClient\n */\nexport interface ContextClientOptions {\n /**\n * Your Context Protocol API key\n * @example \"sk_live_abc123...\"\n */\n apiKey: string;\n\n /**\n * Base URL for the Context Protocol API\n * @default \"https://ctxprotocol.com\"\n */\n baseUrl?: string;\n}\n\n/**\n * An individual MCP tool exposed by a tool listing\n */\nexport interface McpTool {\n /** Name of the MCP tool method */\n name: string;\n\n /** Description of what this method does */\n description: string;\n\n /**\n * JSON Schema for the input arguments this tool accepts.\n * Used by LLMs to generate correct arguments.\n */\n inputSchema?: Record<string, unknown>;\n\n /**\n * JSON Schema for the output this tool returns.\n * Used by LLMs to understand the response structure.\n */\n outputSchema?: Record<string, unknown>;\n}\n\n/**\n * Represents a tool available on the Context Protocol marketplace\n */\nexport interface Tool {\n /** Unique identifier for the tool (UUID) */\n id: string;\n\n /** Human-readable name of the tool */\n name: string;\n\n /** Description of what the tool does */\n description: string;\n\n /** Price per execution in USDC */\n price: string;\n\n /** Tool category (e.g., \"defi\", \"nft\") */\n category?: string;\n\n /** Whether the tool is verified by Context Protocol */\n isVerified?: boolean;\n\n /**\n * Available MCP tool methods\n * Use items from this array as `toolName` when executing\n */\n mcpTools?: McpTool[];\n\n /** Creation timestamp */\n createdAt?: string;\n\n /** Last update timestamp */\n updatedAt?: string;\n}\n\n/**\n * Response from the tools search endpoint\n */\nexport interface SearchResponse {\n /** Array of matching tools */\n tools: Tool[];\n\n /** The search query that was used */\n query: string;\n\n /** Total number of results */\n count: number;\n}\n\n/**\n * Options for searching tools\n */\nexport interface SearchOptions {\n /** Search query (semantic search) */\n query?: string;\n\n /** Maximum number of results (1-50, default 10) */\n limit?: number;\n}\n\n/**\n * Options for executing a tool\n */\nexport interface ExecuteOptions {\n /** The UUID of the tool to execute (from search results) */\n toolId: string;\n\n /** The specific MCP tool name to call (from tool's mcpTools array) */\n toolName: string;\n\n /** Arguments to pass to the tool */\n args?: Record<string, unknown>;\n}\n\n/**\n * Successful execution response from the API\n */\nexport interface ExecuteApiSuccessResponse {\n success: true;\n\n /** The result data from the tool execution */\n result: unknown;\n\n /** Information about the executed tool */\n tool: {\n id: string;\n name: string;\n };\n\n /** Execution duration in milliseconds */\n durationMs: number;\n}\n\n/**\n * Error response from the API\n */\nexport interface ExecuteApiErrorResponse {\n /** Human-readable error message */\n error: string;\n\n /** Error code for programmatic handling */\n code?: ContextErrorCode;\n\n /** URL to help resolve the issue */\n helpUrl?: string;\n}\n\n/**\n * Raw API response from the execute endpoint\n */\nexport type ExecuteApiResponse = ExecuteApiSuccessResponse | ExecuteApiErrorResponse;\n\n/**\n * The resolved result returned to the user after SDK processing\n */\nexport interface ExecutionResult<T = unknown> {\n /** The data returned by the tool */\n result: T;\n\n /** Information about the executed tool */\n tool: {\n id: string;\n name: string;\n };\n\n /** Execution duration in milliseconds */\n durationMs: number;\n}\n\n/**\n * Specific error codes returned by the Context Protocol API\n */\nexport type ContextErrorCode =\n | \"unauthorized\"\n | \"no_wallet\"\n | \"insufficient_allowance\"\n | \"payment_failed\"\n | \"execution_failed\";\n\n/**\n * Error thrown by the Context Protocol client\n */\nexport class ContextError extends Error {\n constructor(\n message: string,\n public readonly code?: ContextErrorCode | string,\n public readonly statusCode?: number,\n public readonly helpUrl?: string\n ) {\n super(message);\n this.name = \"ContextError\";\n }\n}\n","import type { Tool, SearchResponse } from \"../types.js\";\nimport type { ContextClient } from \"../client.js\";\n\n/**\n * Discovery resource for searching and finding tools on the Context Protocol marketplace\n */\nexport class Discovery {\n constructor(private client: ContextClient) {}\n\n /**\n * Search for tools matching a query string\n *\n * @param query - The search query (e.g., \"gas prices\", \"nft metadata\")\n * @param limit - Maximum number of results (1-50, default 10)\n * @returns Array of matching tools\n *\n * @example\n * ```typescript\n * const tools = await client.discovery.search(\"gas prices\");\n * console.log(tools[0].name); // \"Gas Price Oracle\"\n * console.log(tools[0].mcpTools); // Available methods\n * ```\n */\n async search(query: string, limit?: number): Promise<Tool[]> {\n const params = new URLSearchParams();\n\n if (query) {\n params.set(\"q\", query);\n }\n\n if (limit !== undefined) {\n params.set(\"limit\", String(limit));\n }\n\n const queryString = params.toString();\n const endpoint = `/api/v1/tools/search${queryString ? `?${queryString}` : \"\"}`;\n\n const response = await this.client.fetch<SearchResponse>(endpoint);\n\n return response.tools;\n }\n\n /**\n * Get featured/popular tools (empty query search)\n *\n * @param limit - Maximum number of results (1-50, default 10)\n * @returns Array of featured tools\n *\n * @example\n * ```typescript\n * const featured = await client.discovery.getFeatured(5);\n * ```\n */\n async getFeatured(limit?: number): Promise<Tool[]> {\n return this.search(\"\", limit);\n }\n}\n","import type {\n ExecuteOptions,\n ExecuteApiResponse,\n ExecutionResult,\n} from \"../types.js\";\nimport { ContextError } from \"../types.js\";\nimport type { ContextClient } from \"../client.js\";\n\n/**\n * Tools resource for executing tools on the Context Protocol marketplace\n */\nexport class Tools {\n constructor(private client: ContextClient) {}\n\n /**\n * Execute a tool with the provided arguments\n *\n * @param options - Execution options\n * @param options.toolId - The UUID of the tool (from search results)\n * @param options.toolName - The specific MCP tool method to call (from tool's mcpTools array)\n * @param options.args - Arguments to pass to the tool\n * @returns The execution result with the tool's output data\n *\n * @throws {ContextError} With code `no_wallet` if wallet not set up\n * @throws {ContextError} With code `insufficient_allowance` if Auto Pay not enabled\n * @throws {ContextError} With code `payment_failed` if on-chain payment fails\n * @throws {ContextError} With code `execution_failed` if tool execution fails\n *\n * @example\n * ```typescript\n * // First, search for a tool\n * const tools = await client.discovery.search(\"gas prices\");\n * const tool = tools[0];\n *\n * // Execute a specific method from the tool's mcpTools\n * const result = await client.tools.execute({\n * toolId: tool.id,\n * toolName: tool.mcpTools[0].name, // e.g., \"get_gas_prices\"\n * args: { chainId: 1 }\n * });\n *\n * console.log(result.result); // The tool's output\n * console.log(result.durationMs); // Execution time\n * ```\n */\n async execute<T = unknown>(options: ExecuteOptions): Promise<ExecutionResult<T>> {\n const { toolId, toolName, args } = options;\n\n const response = await this.client.fetch<ExecuteApiResponse>(\n \"/api/v1/tools/execute\",\n {\n method: \"POST\",\n body: JSON.stringify({ toolId, toolName, args }),\n }\n );\n\n // Handle error response\n if (\"error\" in response) {\n throw new ContextError(\n response.error,\n response.code,\n 400,\n response.helpUrl\n );\n }\n\n // Handle success response\n if (response.success) {\n return {\n result: response.result as T,\n tool: response.tool,\n durationMs: response.durationMs,\n };\n }\n\n // Fallback - shouldn't reach here with valid API responses\n throw new ContextError(\"Unexpected response format from API\");\n }\n}\n","import type { ContextClientOptions } from \"./types.js\";\nimport { ContextError } from \"./types.js\";\nimport { Discovery } from \"./resources/discovery.js\";\nimport { Tools } from \"./resources/tools.js\";\n\n/**\n * The official TypeScript client for the Context Protocol.\n *\n * Use this client to discover and execute AI tools programmatically.\n *\n * @example\n * ```typescript\n * import { ContextClient } from \"@contextprotocol/client\";\n *\n * const client = new ContextClient({\n * apiKey: \"sk_live_...\"\n * });\n *\n * // Discover tools\n * const tools = await client.discovery.search(\"gas prices\");\n *\n * // Execute a tool method\n * const result = await client.tools.execute({\n * toolId: tools[0].id,\n * toolName: tools[0].mcpTools[0].name,\n * args: { chainId: 1 }\n * });\n * ```\n */\nexport class ContextClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n /**\n * Discovery resource for searching tools\n */\n public readonly discovery: Discovery;\n\n /**\n * Tools resource for executing tools\n */\n public readonly tools: Tools;\n\n /**\n * Creates a new Context Protocol client\n *\n * @param options - Client configuration options\n * @param options.apiKey - Your Context Protocol API key (format: sk_live_...)\n * @param options.baseUrl - Optional base URL override (defaults to https://ctxprotocol.com)\n */\n constructor(options: ContextClientOptions) {\n if (!options.apiKey) {\n throw new ContextError(\"API key is required\");\n }\n\n this.apiKey = options.apiKey;\n this.baseUrl = (options.baseUrl ?? \"https://ctxprotocol.com\").replace(/\\/$/, \"\");\n\n // Initialize resources\n this.discovery = new Discovery(this);\n this.tools = new Tools(this);\n }\n\n /**\n * Internal method for making authenticated HTTP requests\n * All requests include the Authorization header with the API key\n *\n * @internal\n */\n async fetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`;\n\n const response = await fetch(url, {\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n let errorMessage = `HTTP ${response.status}: ${response.statusText}`;\n let errorCode: string | undefined;\n let helpUrl: string | undefined;\n\n try {\n const errorBody = await response.json();\n if (errorBody.error) {\n errorMessage = errorBody.error;\n errorCode = errorBody.code;\n helpUrl = errorBody.helpUrl;\n }\n } catch {\n // Use default error message if JSON parsing fails\n }\n\n throw new ContextError(errorMessage, errorCode, response.status, helpUrl);\n }\n\n return response.json() as Promise<T>;\n }\n}\n","/**\n * Context types for portfolio and protocol data injection.\n *\n * These types allow MCP tools to receive personalized user context\n * (wallet addresses, positions, balances) for analysis.\n *\n * =============================================================================\n * DECLARING CONTEXT REQUIREMENTS\n * =============================================================================\n *\n * Since the MCP protocol only transmits standard fields (name, description,\n * inputSchema, outputSchema), context requirements MUST be embedded in the\n * inputSchema using the \"x-context-requirements\" JSON Schema extension.\n *\n * @example\n * ```typescript\n * import { CONTEXT_REQUIREMENTS_KEY, type ContextRequirementType } from \"@ctxprotocol/sdk\";\n * import type { HyperliquidContext } from \"@ctxprotocol/sdk\";\n *\n * const tool = {\n * name: \"analyze_my_positions\",\n * inputSchema: {\n * type: \"object\",\n * [CONTEXT_REQUIREMENTS_KEY]: [\"hyperliquid\"] as ContextRequirementType[],\n * properties: {\n * portfolio: { type: \"object\" }\n * },\n * required: [\"portfolio\"]\n * }\n * };\n *\n * // Your handler receives the injected context:\n * function handleAnalyzeMyPositions(args: { portfolio: HyperliquidContext }) {\n * const { perpPositions, accountSummary } = args.portfolio;\n * // ... analyze and return insights\n * }\n * ```\n *\n * @packageDocumentation\n */\n\n// Wallet context types\nexport * from \"./wallet.js\";\n\n// Protocol-specific context types\nexport * from \"./polymarket.js\";\nexport * from \"./hyperliquid.js\";\n\n// Re-import for composite type\nimport type { WalletContext, ERC20Context } from \"./wallet.js\";\nimport type { PolymarketContext } from \"./polymarket.js\";\nimport type { HyperliquidContext } from \"./hyperliquid.js\";\n\n// ============================================================================\n// CONTEXT REQUIREMENTS\n//\n// MCP tools that need user portfolio data MUST declare this in inputSchema.\n// The MCP protocol only transmits standard fields (name, description,\n// inputSchema, outputSchema). Custom fields get stripped by the MCP SDK.\n// ============================================================================\n\n/**\n * JSON Schema extension key for declaring context requirements.\n *\n * WHY THIS APPROACH?\n * - MCP protocol only transmits: name, description, inputSchema, outputSchema\n * - Custom fields like `requirements` get stripped by MCP SDK during transport\n * - JSON Schema allows custom \"x-\" prefixed extension properties\n * - inputSchema is preserved end-to-end through MCP transport\n *\n * @example\n * ```typescript\n * import { CONTEXT_REQUIREMENTS_KEY } from \"@ctxprotocol/sdk\";\n *\n * const tool = {\n * name: \"analyze_my_positions\",\n * inputSchema: {\n * type: \"object\",\n * [CONTEXT_REQUIREMENTS_KEY]: [\"hyperliquid\"],\n * properties: { portfolio: { type: \"object\" } },\n * required: [\"portfolio\"]\n * }\n * };\n * ```\n */\nexport const CONTEXT_REQUIREMENTS_KEY = \"x-context-requirements\" as const;\n\n/**\n * Context requirement types supported by the Context marketplace.\n * Maps to protocol-specific context builders on the platform.\n *\n * @example\n * ```typescript\n * inputSchema: {\n * type: \"object\",\n * \"x-context-requirements\": [\"hyperliquid\"] as ContextRequirementType[],\n * properties: { portfolio: { type: \"object\" } },\n * required: [\"portfolio\"]\n * }\n * ```\n */\nexport type ContextRequirementType = \"polymarket\" | \"hyperliquid\" | \"wallet\";\n\n/**\n * @deprecated The `requirements` field at tool level gets stripped by MCP SDK.\n * Use `x-context-requirements` inside `inputSchema` instead.\n *\n * @example\n * ```typescript\n * // ❌ OLD (doesn't work - stripped by MCP SDK)\n * { requirements: { context: [\"hyperliquid\"] } }\n *\n * // ✅ NEW (works - preserved through MCP transport)\n * { inputSchema: { \"x-context-requirements\": [\"hyperliquid\"], ... } }\n * ```\n */\nexport interface ToolRequirements {\n /**\n * @deprecated Use `x-context-requirements` in inputSchema instead.\n */\n context?: ContextRequirementType[];\n}\n\n/**\n * Composite context for tools that need multiple data sources.\n *\n * This is the unified structure that can be passed to MCP tools\n * to provide comprehensive user context.\n */\nexport interface UserContext {\n /** Base wallet information */\n wallet?: WalletContext;\n /** ERC20 token holdings */\n erc20?: ERC20Context;\n /** Polymarket positions and orders */\n polymarket?: PolymarketContext;\n /** Hyperliquid perpetual positions and account data */\n hyperliquid?: HyperliquidContext;\n // Future protocols:\n // aave?: AaveContext;\n}\n","import { jwtVerify, importSPKI, type JWTPayload } from \"jose\";\nimport { ContextError } from \"../client/types.js\";\n\n// ============================================================================\n// Express-compatible types (avoid requiring express as a dependency)\n// ============================================================================\n\ninterface ContextRequest {\n headers: {\n authorization?: string;\n [key: string]: string | string[] | undefined;\n };\n body?: {\n method?: string;\n [key: string]: unknown;\n };\n context?: JWTPayload;\n}\n\ninterface ContextResponse {\n status(code: number): ContextResponse;\n json(data: unknown): void;\n}\n\ntype NextFunction = (error?: unknown) => void;\n\n/**\n * Extended Request object with verified Context Protocol JWT payload.\n *\n * After `createContextMiddleware()` runs successfully on a protected method,\n * the `context` property contains the decoded JWT claims.\n */\nexport interface ContextMiddlewareRequest extends ContextRequest {\n /** The verified JWT payload from Context Protocol (available after auth) */\n context?: JWTPayload;\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n// The Context Protocol Public Key\n// In a real scenario, this might be fetched from a well-known URL or passed in config.\n// For now, we hardcode the Official Platform Public Key.\n// Official Context Protocol Platform Public Key (RS256)\nconst CONTEXT_PLATFORM_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs9YOgdpkmVQ5aoNovjsu\nchJdV54OT7dUdbVXz914a7Px8EwnpDqhsvG7WO8xL8sj2Rn6ueAJBk+04Hy/P/UN\nRJyp23XL5TsGmb4rbfg0ii0MiL2nbVXuqvAe3JSM2BOFZR5bpwIVIaa8aonfamUy\nVXGc7OosF90ThdKjm9cXlVM+kV6IgSWc1502X7M3abQqRcTU/rluVXnky0eiWDQa\nlfOKbr7w0u72dZjiZPwnNDsX6PEEgvfmoautTFYTQgnZjDzq8UimTcv3KF+hJ5Ep\nweipe6amt9lzQzi8WXaFKpOXHQs//WDlUytz/Hl8pvd5craZKzo6Kyrg1Vfan7H3\nTQIDAQAB\n-----END PUBLIC KEY-----`;\n\n/**\n * MCP methods that require authentication.\n * - tools/call: Executes tool logic, may cost money\n * - resources/read: Reads potentially sensitive data\n * - prompts/get: Gets prompt content\n */\nconst PROTECTED_MCP_METHODS = new Set([\n \"tools/call\",\n // Uncomment these if you want to protect resource/prompt access:\n // \"resources/read\",\n // \"prompts/get\",\n]);\n\n/**\n * MCP methods that are always open (no auth required).\n * These are discovery/listing operations that return metadata only.\n */\nconst OPEN_MCP_METHODS = new Set([\n \"initialize\",\n \"tools/list\",\n \"resources/list\",\n \"prompts/list\",\n \"ping\",\n \"notifications/initialized\",\n]);\n\n// ============================================================================\n// Method Classification\n// ============================================================================\n\n/**\n * Determines if a given MCP method requires authentication.\n *\n * Discovery methods (tools/list, resources/list, etc.) are open.\n * Execution methods (tools/call) require authentication.\n *\n * @param method The MCP JSON-RPC method (e.g., \"tools/list\", \"tools/call\")\n * @returns true if the method requires authentication\n *\n * @example\n * ```typescript\n * if (isProtectedMcpMethod(body.method)) {\n * await verifyContextRequest({ authorizationHeader: req.headers.authorization });\n * }\n * ```\n */\nexport function isProtectedMcpMethod(method: string): boolean {\n return PROTECTED_MCP_METHODS.has(method);\n}\n\n/**\n * Determines if a given MCP method is explicitly open (no auth).\n *\n * @param method The MCP JSON-RPC method\n * @returns true if the method is known to be open\n */\nexport function isOpenMcpMethod(method: string): boolean {\n return OPEN_MCP_METHODS.has(method);\n}\n\n// ============================================================================\n// Request Verification\n// ============================================================================\n\nexport interface VerifyRequestOptions {\n /** The full Authorization header string (e.g. \"Bearer eyJ...\") */\n authorizationHeader?: string;\n /** Expected Audience (your tool URL) for stricter validation */\n audience?: string;\n}\n\n/**\n * Verifies that an incoming request originated from the Context Protocol Platform.\n *\n * @param options Contains the Authorization header\n * @returns The decoded payload if valid\n * @throws ContextError if invalid\n */\nexport async function verifyContextRequest(options: VerifyRequestOptions) {\n const { authorizationHeader, audience } = options;\n\n if (!authorizationHeader || !authorizationHeader.startsWith(\"Bearer \")) {\n throw new ContextError(\n \"Missing or invalid Authorization header\",\n \"unauthorized\",\n 401\n );\n }\n\n const token = authorizationHeader.split(\" \")[1];\n\n try {\n const publicKey = await importSPKI(CONTEXT_PLATFORM_PUBLIC_KEY_PEM, \"RS256\");\n\n const { payload } = await jwtVerify(token, publicKey, {\n issuer: \"https://ctxprotocol.com\",\n audience: audience,\n });\n\n return payload;\n } catch (error) {\n throw new ContextError(\n \"Invalid Context Protocol signature\",\n \"unauthorized\",\n 401\n );\n }\n}\n\n// ============================================================================\n// Easy-Mode Middleware\n// ============================================================================\n\nexport interface CreateContextMiddlewareOptions {\n /** Expected Audience (your tool URL) for stricter validation */\n audience?: string;\n}\n\n/**\n * Creates an Express/Connect-compatible middleware that secures your MCP endpoint.\n *\n * This is the \"1 line of code\" solution to secure your MCP server.\n * It automatically:\n * - Allows discovery methods (tools/list, initialize) without authentication\n * - Requires and verifies JWT for execution methods (tools/call)\n * - Attaches the verified payload to `req.context` for downstream use\n *\n * @param options Optional configuration\n * @returns Express-compatible middleware function\n *\n * @example\n * ```typescript\n * import express from \"express\";\n * import { createContextMiddleware } from \"@ctxprotocol/sdk\";\n *\n * const app = express();\n * app.use(express.json());\n *\n * // 1 line to secure your endpoint\n * app.use(\"/mcp\", createContextMiddleware());\n *\n * app.post(\"/mcp\", (req, res) => {\n * // req.context contains verified JWT payload (on protected methods)\n * // Handle MCP request...\n * });\n * ```\n */\nexport function createContextMiddleware(options: CreateContextMiddlewareOptions = {}) {\n return async function contextMiddleware(\n req: ContextRequest,\n res: ContextResponse,\n next: NextFunction\n ): Promise<void> {\n const method = req.body?.method as string | undefined;\n\n // Allow discovery methods without authentication\n // Discovery methods (tools/list, initialize, etc.) are open by design\n if (!method || !isProtectedMcpMethod(method)) {\n return next();\n }\n\n // Protected method - require authentication\n try {\n const payload = await verifyContextRequest({\n authorizationHeader: req.headers.authorization,\n audience: options.audience,\n });\n\n // Attach verified payload to request for downstream handlers\n req.context = payload;\n next();\n } catch (error) {\n const statusCode = error instanceof ContextError ? error.statusCode || 401 : 401;\n res.status(statusCode).json({ error: \"Unauthorized\" });\n }\n };\n}\n\n\n","/**\n * Handshake Types for MCP Tool Developers\n *\n * Use these types when your tool needs to request user interaction\n * before completing an action (signatures, transactions, OAuth).\n *\n * @see https://docs.ctxprotocol.com/guides/handshake-architecture\n *\n * ## Usage Pattern\n *\n * Tools return handshake actions in the `_meta.handshakeAction` field\n * of their MCP response. The Context platform intercepts these and\n * presents the appropriate UI to the user.\n *\n * ## Action Types\n *\n * - `signature_request`: For EIP-712 signatures (Hyperliquid, Polymarket, etc.)\n * - `transaction_proposal`: For direct on-chain transactions (Uniswap, NFT mints)\n * - `auth_required`: For OAuth flows (Discord, Twitter, etc.)\n */\n\n// === Shared Meta Type ===\n\nexport type HandshakeMeta = {\n /** Human-readable description of the action */\n description: string;\n /** Protocol name (e.g., \"Hyperliquid\", \"Polymarket\") */\n protocol?: string;\n /** Action verb (e.g., \"Place Order\", \"Place Bid\") */\n action?: string;\n /** Token symbol if relevant */\n tokenSymbol?: string;\n /** Human-readable token amount */\n tokenAmount?: string;\n /** UI warning level */\n warningLevel?: \"info\" | \"caution\" | \"danger\";\n};\n\n// === Web3: Signature Requests (for proxy wallet platforms) ===\n\nexport type EIP712Domain = {\n /** Domain name (e.g., \"Hyperliquid\", \"ClobAuthDomain\") */\n name: string;\n /** Domain version */\n version: string;\n /** Chain ID (informational - signing is chain-agnostic) */\n chainId: number;\n /** Optional verifying contract address */\n verifyingContract?: `0x${string}`;\n};\n\nexport type EIP712TypeField = {\n name: string;\n type: string;\n};\n\n/**\n * Signature Request\n *\n * Use this for platforms with proxy wallets (Hyperliquid, Polymarket, dYdX).\n *\n * Benefits:\n * - No gas required (user signs a message, not a transaction)\n * - No network switching needed (signing is chain-agnostic)\n * - Works with Privy embedded wallets on any chain\n *\n * @example\n * ```typescript\n * return {\n * structuredContent: {\n * _meta: {\n * handshakeAction: createSignatureRequest({\n * domain: { name: \"Hyperliquid\", version: \"1\", chainId: 42161 },\n * types: { Order: [...] },\n * primaryType: \"Order\",\n * message: { asset: 4, isBuy: true, ... },\n * meta: { description: \"Place Long ETH order\", protocol: \"Hyperliquid\" }\n * })\n * }\n * }\n * };\n * ```\n */\nexport type SignatureRequest = {\n _action: \"signature_request\";\n /** EIP-712 domain separator */\n domain: EIP712Domain;\n /** EIP-712 type definitions */\n types: Record<string, EIP712TypeField[]>;\n /** The primary type being signed */\n primaryType: string;\n /** The message data to sign */\n message: Record<string, unknown>;\n /** UI metadata for the approval card */\n meta?: HandshakeMeta;\n /**\n * Optional: Tool name to call with the signature result.\n * If provided, the platform will call this tool with { signature, originalParams }\n * after the user signs.\n */\n callbackToolName?: string;\n};\n\n// === Web3: Transaction Proposals (for direct on-chain actions) ===\n\nexport type TransactionProposalMeta = HandshakeMeta & {\n /** Estimated gas cost (informational - Context may sponsor) */\n estimatedGas?: string;\n /** Link to contract on block explorer */\n explorerUrl?: string;\n};\n\n/**\n * Transaction Proposal\n *\n * Use this for protocols without proxy wallets (Uniswap, NFT mints, etc.).\n *\n * Note: May require network switching and gas fees.\n *\n * @example\n * ```typescript\n * return {\n * structuredContent: {\n * _meta: {\n * handshakeAction: createTransactionProposal({\n * chainId: 8453,\n * to: \"0x...\",\n * data: \"0x...\",\n * meta: { description: \"Swap 100 USDC for ETH\", protocol: \"Uniswap\" }\n * })\n * }\n * }\n * };\n * ```\n */\nexport type TransactionProposal = {\n _action: \"transaction_proposal\";\n /** EVM chain ID (e.g., 137 for Polygon, 8453 for Base) */\n chainId: number;\n /** Target contract address */\n to: `0x${string}`;\n /** Encoded calldata */\n data: `0x${string}`;\n /** Wei to send (as string, default \"0\") */\n value?: string;\n /** UI metadata for the approval card */\n meta?: TransactionProposalMeta;\n};\n\n// === Web2: OAuth Requests ===\n\nexport type AuthRequiredMeta = {\n /** Human-friendly service name */\n displayName?: string;\n /** Permissions being requested */\n scopes?: string[];\n /** Description of what access is needed */\n description?: string;\n /** Tool's icon URL */\n iconUrl?: string;\n /** How long authorization lasts */\n expiresIn?: string;\n};\n\n/**\n * Auth Required\n *\n * Use this when your tool needs the user to authenticate with an external service.\n *\n * @example\n * ```typescript\n * if (!hasUserToken(contextDid)) {\n * return {\n * structuredContent: {\n * _meta: {\n * handshakeAction: createAuthRequired({\n * provider: \"discord\",\n * authUrl: \"https://your-server.com/oauth/discord\",\n * meta: { displayName: \"Discord Bot\", scopes: [\"send_messages\"] }\n * })\n * }\n * }\n * };\n * }\n * ```\n */\nexport type AuthRequired = {\n _action: \"auth_required\";\n /** Service identifier (e.g., \"discord\", \"slack\") */\n provider: string;\n /** Your OAuth initiation endpoint (MUST be HTTPS) */\n authUrl: string;\n /** UI metadata for the auth card */\n meta?: AuthRequiredMeta;\n};\n\n// === Union Type ===\n\nexport type HandshakeAction =\n | SignatureRequest\n | TransactionProposal\n | AuthRequired;\n\n// === Type Guards ===\n\nexport function isHandshakeAction(value: unknown): value is HandshakeAction {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"_action\" in value &&\n ((value as { _action: string })._action === \"signature_request\" ||\n (value as { _action: string })._action === \"transaction_proposal\" ||\n (value as { _action: string })._action === \"auth_required\")\n );\n}\n\nexport function isSignatureRequest(value: unknown): value is SignatureRequest {\n return isHandshakeAction(value) && value._action === \"signature_request\";\n}\n\nexport function isTransactionProposal(\n value: unknown\n): value is TransactionProposal {\n return isHandshakeAction(value) && value._action === \"transaction_proposal\";\n}\n\nexport function isAuthRequired(value: unknown): value is AuthRequired {\n return isHandshakeAction(value) && value._action === \"auth_required\";\n}\n\n// === Helper Functions for Tool Developers ===\n\n/**\n * Create a signature request response.\n * Return this from your tool when you need the user to sign EIP-712 typed data.\n *\n * Use this for platforms with proxy wallets (Hyperliquid, Polymarket, dYdX).\n * Benefits: No gas required, no network switching needed.\n */\nexport function createSignatureRequest(\n params: Omit<SignatureRequest, \"_action\">\n): SignatureRequest {\n return {\n _action: \"signature_request\",\n ...params,\n };\n}\n\n/**\n * Create a transaction proposal response.\n * Return this from your tool when you need the user to sign a direct on-chain transaction.\n *\n * Use this for protocols that don't use proxy wallets (Uniswap, NFT mints, etc.).\n * Note: May require network switching and gas.\n */\nexport function createTransactionProposal(\n params: Omit<TransactionProposal, \"_action\">\n): TransactionProposal {\n return {\n _action: \"transaction_proposal\",\n ...params,\n };\n}\n\n/**\n * Create an auth required response.\n * Return this from your tool when you need the user to authenticate via OAuth.\n */\nexport function createAuthRequired(\n params: Omit<AuthRequired, \"_action\">\n): AuthRequired {\n return {\n _action: \"auth_required\",\n ...params,\n };\n}\n\n// === MCP Response Helper ===\n\n/**\n * Wrap a handshake action in the proper MCP response format.\n *\n * MCP tools should return handshake actions in `_meta.handshakeAction` to prevent\n * the MCP SDK from stripping unknown fields.\n *\n * @example\n * ```typescript\n * // In your tool handler:\n * return wrapHandshakeResponse(createSignatureRequest({\n * domain: { name: \"Hyperliquid\", version: \"1\", chainId: 42161 },\n * types: { Order: [...] },\n * primaryType: \"Order\",\n * message: orderData,\n * meta: { description: \"Place order\", protocol: \"Hyperliquid\" }\n * }));\n * ```\n */\nexport function wrapHandshakeResponse(action: HandshakeAction): {\n content: Array<{ type: \"text\"; text: string }>;\n structuredContent: {\n _meta: { handshakeAction: HandshakeAction };\n status: string;\n message: string;\n };\n} {\n const actionType = action._action.replace(\"_\", \" \");\n return {\n content: [\n {\n type: \"text\",\n text: `Handshake required: ${actionType}. Please approve in the Context app.`,\n },\n ],\n structuredContent: {\n _meta: {\n handshakeAction: action,\n },\n status: \"handshake_required\",\n message: action.meta?.description ?? `${actionType} required`,\n },\n };\n}\n"]}
|
package/package.json
CHANGED