@ctxprotocol/sdk 0.5.4 → 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 CHANGED
@@ -9,23 +9,41 @@ Context Protocol is **npm for AI capabilities**. Just as you install packages to
9
9
  [![npm version](https://img.shields.io/npm/v/@ctxprotocol/sdk.svg)](https://www.npmjs.com/package/@ctxprotocol/sdk)
10
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
- - **💸 Micro-Billing:** Pay only for what you use (e.g., $0.001/query). No monthly subscriptions for tools you rarely use.
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 + Context extensions |
38
+ | **Tool Contributor (Data Broker)** | `@modelcontextprotocol/sdk` + `@ctxprotocol/sdk` — Standard MCP server + security middleware |
27
39
 
28
- If you're building an MCP server to contribute tools and earn money, you **don't need this SDK**. See [Building MCP Servers](#building-mcp-servers-tool-contributors) for the simple pattern.
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 { CONTEXT_REQUIREMENTS_KEY, type ContextRequirementType } from "@ctxprotocol/sdk";
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
- /** JSON Schema extension key for declaring context requirements */
339
- const CONTEXT_REQUIREMENTS_KEY = "x-context-requirements";
340
-
341
- // Usage in inputSchema:
342
- inputSchema: {
343
- type: "object",
344
- [CONTEXT_REQUIREMENTS_KEY]: ["hyperliquid"] as ContextRequirementType[],
345
- properties: { portfolio: { type: "object" } },
346
- required: ["portfolio"]
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 | Reason |
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 (Portfolio Analysis Tools)
535
+ ### Context Injection (Personalized Tools)
509
536
 
510
- Building tools that analyze user portfolios? Context automatically injects user portfolio data into your toolsno authentication required.
537
+ Building tools that analyze user data? Context automatically injects user context into your tools no authentication required.
511
538
 
512
- **📖 Read the full guide: [Context Injection Architecture](./docs/context-injection.md)**
513
-
514
- Key benefits:
515
- - **No Auth Required** User data is injected automatically from their linked wallets
516
- - **Type-Safe** Use SDK types like `PolymarketContext`, `HyperliquidContext`
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 the `x-context-requirements` JSON Schema extension inside `inputSchema`:
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 { CONTEXT_REQUIREMENTS_KEY, type ContextRequirementType } from "@ctxprotocol/sdk";
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
- portfolio: {
601
+ wallet: {
537
602
  type: "object",
538
- description: "Portfolio context (injected by platform)",
603
+ description: "Wallet context (injected by platform)",
539
604
  },
540
605
  },
541
- required: ["portfolio"],
606
+ required: ["wallet"],
542
607
  },
543
608
  outputSchema: { /* ... */ },
544
609
  }];
545
610
  ```
546
611
 
547
- **Why `x-context-requirements` in inputSchema (not a top-level field)?**
612
+ **Why `_meta` at the tool level?**
548
613
 
549
- The MCP protocol only transmits standard fields (`name`, `description`, `inputSchema`, `outputSchema`). Custom top-level fields like `requirements` get **stripped** by the MCP SDK during `listTools()` transport. JSON Schema allows `x-` prefixed extension properties, and `inputSchema` is preserved through transport.
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
@@ -191,7 +191,13 @@ var ContextClient = class {
191
191
  // src/context/index.ts
192
192
  var CONTEXT_REQUIREMENTS_KEY = "x-context-requirements";
193
193
  var CONTEXT_PLATFORM_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
194
- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
194
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs9YOgdpkmVQ5aoNovjsu
195
+ chJdV54OT7dUdbVXz914a7Px8EwnpDqhsvG7WO8xL8sj2Rn6ueAJBk+04Hy/P/UN
196
+ RJyp23XL5TsGmb4rbfg0ii0MiL2nbVXuqvAe3JSM2BOFZR5bpwIVIaa8aonfamUy
197
+ VXGc7OosF90ThdKjm9cXlVM+kV6IgSWc1502X7M3abQqRcTU/rluVXnky0eiWDQa
198
+ lfOKbr7w0u72dZjiZPwnNDsX6PEEgvfmoautTFYTQgnZjDzq8UimTcv3KF+hJ5Ep
199
+ weipe6amt9lzQzi8WXaFKpOXHQs//WDlUytz/Hl8pvd5craZKzo6Kyrg1Vfan7H3
200
+ TQIDAQAB
195
201
  -----END PUBLIC KEY-----`;
196
202
  var PROTECTED_MCP_METHODS = /* @__PURE__ */ new Set([
197
203
  "tools/call"
@@ -258,14 +264,72 @@ function createContextMiddleware(options = {}) {
258
264
  };
259
265
  }
260
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
+
261
317
  exports.CONTEXT_REQUIREMENTS_KEY = CONTEXT_REQUIREMENTS_KEY;
262
318
  exports.ContextClient = ContextClient;
263
319
  exports.ContextError = ContextError;
264
320
  exports.Discovery = Discovery;
265
321
  exports.Tools = Tools;
322
+ exports.createAuthRequired = createAuthRequired;
266
323
  exports.createContextMiddleware = createContextMiddleware;
324
+ exports.createSignatureRequest = createSignatureRequest;
325
+ exports.createTransactionProposal = createTransactionProposal;
326
+ exports.isAuthRequired = isAuthRequired;
327
+ exports.isHandshakeAction = isHandshakeAction;
267
328
  exports.isOpenMcpMethod = isOpenMcpMethod;
268
329
  exports.isProtectedMcpMethod = isProtectedMcpMethod;
330
+ exports.isSignatureRequest = isSignatureRequest;
331
+ exports.isTransactionProposal = isTransactionProposal;
269
332
  exports.verifyContextRequest = verifyContextRequest;
333
+ exports.wrapHandshakeResponse = wrapHandshakeResponse;
270
334
  //# sourceMappingURL=index.cjs.map
271
335
  //# sourceMappingURL=index.cjs.map
@@ -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// TODO: REPLACE THIS WITH THE ACTUAL GENERATED PUBLIC KEY FROM THE PLATFORM SETUP\nconst CONTEXT_PLATFORM_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\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"]}
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
- export { CONTEXT_REQUIREMENTS_KEY, type ContextMiddlewareRequest, type ContextRequirementType, type CreateContextMiddlewareOptions, type ERC20Context, type ERC20TokenBalance, type HyperliquidAccountSummary, type HyperliquidContext, type HyperliquidOrder, type HyperliquidPerpPosition, type HyperliquidSpotBalance, type PolymarketContext, type PolymarketOrder, type PolymarketPosition, type ToolRequirements, type UserContext, type VerifyRequestOptions, type WalletContext, createContextMiddleware, isOpenMcpMethod, isProtectedMcpMethod, verifyContextRequest };
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
- export { CONTEXT_REQUIREMENTS_KEY, type ContextMiddlewareRequest, type ContextRequirementType, type CreateContextMiddlewareOptions, type ERC20Context, type ERC20TokenBalance, type HyperliquidAccountSummary, type HyperliquidContext, type HyperliquidOrder, type HyperliquidPerpPosition, type HyperliquidSpotBalance, type PolymarketContext, type PolymarketOrder, type PolymarketPosition, type ToolRequirements, type UserContext, type VerifyRequestOptions, type WalletContext, createContextMiddleware, isOpenMcpMethod, isProtectedMcpMethod, verifyContextRequest };
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
@@ -189,7 +189,13 @@ var ContextClient = class {
189
189
  // src/context/index.ts
190
190
  var CONTEXT_REQUIREMENTS_KEY = "x-context-requirements";
191
191
  var CONTEXT_PLATFORM_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
192
- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
192
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs9YOgdpkmVQ5aoNovjsu
193
+ chJdV54OT7dUdbVXz914a7Px8EwnpDqhsvG7WO8xL8sj2Rn6ueAJBk+04Hy/P/UN
194
+ RJyp23XL5TsGmb4rbfg0ii0MiL2nbVXuqvAe3JSM2BOFZR5bpwIVIaa8aonfamUy
195
+ VXGc7OosF90ThdKjm9cXlVM+kV6IgSWc1502X7M3abQqRcTU/rluVXnky0eiWDQa
196
+ lfOKbr7w0u72dZjiZPwnNDsX6PEEgvfmoautTFYTQgnZjDzq8UimTcv3KF+hJ5Ep
197
+ weipe6amt9lzQzi8WXaFKpOXHQs//WDlUytz/Hl8pvd5craZKzo6Kyrg1Vfan7H3
198
+ TQIDAQAB
193
199
  -----END PUBLIC KEY-----`;
194
200
  var PROTECTED_MCP_METHODS = /* @__PURE__ */ new Set([
195
201
  "tools/call"
@@ -256,6 +262,56 @@ function createContextMiddleware(options = {}) {
256
262
  };
257
263
  }
258
264
 
259
- export { CONTEXT_REQUIREMENTS_KEY, ContextClient, ContextError, Discovery, Tools, createContextMiddleware, isOpenMcpMethod, isProtectedMcpMethod, verifyContextRequest };
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 };
260
316
  //# sourceMappingURL=index.js.map
261
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// TODO: REPLACE THIS WITH THE ACTUAL GENERATED PUBLIC KEY FROM THE PLATFORM SETUP\nconst CONTEXT_PLATFORM_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\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"]}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctxprotocol/sdk",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "Official TypeScript SDK for the Context Protocol - Discover and execute AI tools programmatically",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",