@ctxprotocol/sdk 0.5.5 → 0.6.0

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/dist/index.d.ts CHANGED
@@ -90,8 +90,14 @@ interface PolymarketOrder {
90
90
  * This is what gets passed to MCP tools for personalized analysis.
91
91
  */
92
92
  interface PolymarketContext {
93
- /** The wallet address this context is for */
93
+ /** The wallet address this context is for (may be comma-separated if multiple) */
94
94
  walletAddress: string;
95
+ /**
96
+ * The active wallet address for signing (the one with Polymarket activity).
97
+ * When multiple wallets are linked, this is the wallet that should be used
98
+ * for placing orders. Determined by activity detection on the client.
99
+ */
100
+ activeWalletAddress?: string;
95
101
  /** All open positions */
96
102
  positions: PolymarketPosition[];
97
103
  /** All open orders */
@@ -228,9 +234,11 @@ interface HyperliquidContext {
228
234
  * DECLARING CONTEXT REQUIREMENTS
229
235
  * =============================================================================
230
236
  *
231
- * Since the MCP protocol only transmits standard fields (name, description,
232
- * inputSchema, outputSchema), context requirements MUST be embedded in the
233
- * inputSchema using the "x-context-requirements" JSON Schema extension.
237
+ * Context requirements are declared via `_meta.contextRequirements` at the tool level.
238
+ * This is the primary mechanism that the Context Platform reads.
239
+ *
240
+ * Previously, `x-context-requirements` in inputSchema was recommended, but the MCP SDK
241
+ * may strip extension properties during transport. Use `_meta` instead.
234
242
  *
235
243
  * @example
236
244
  * ```typescript
@@ -239,9 +247,11 @@ interface HyperliquidContext {
239
247
  *
240
248
  * const tool = {
241
249
  * name: "analyze_my_positions",
250
+ * _meta: {
251
+ * contextRequirements: ["hyperliquid"] as ContextRequirementType[],
252
+ * },
242
253
  * inputSchema: {
243
254
  * type: "object",
244
- * [CONTEXT_REQUIREMENTS_KEY]: ["hyperliquid"] as ContextRequirementType[],
245
255
  * properties: {
246
256
  * portfolio: { type: "object" }
247
257
  * },
@@ -260,30 +270,33 @@ interface HyperliquidContext {
260
270
  */
261
271
 
262
272
  /**
263
- * JSON Schema extension key for declaring context requirements.
273
+ * @deprecated Use `_meta.contextRequirements` instead (see META_CONTEXT_REQUIREMENTS_KEY).
264
274
  *
265
- * WHY THIS APPROACH?
266
- * - MCP protocol only transmits: name, description, inputSchema, outputSchema
267
- * - Custom fields like `requirements` get stripped by MCP SDK during transport
268
- * - JSON Schema allows custom "x-" prefixed extension properties
269
- * - inputSchema is preserved end-to-end through MCP transport
275
+ * This key was designed for embedding requirements in inputSchema,
276
+ * but the MCP SDK may strip `x-` prefixed extension properties during transport.
277
+ * The `_meta.contextRequirements` approach is what the Context Platform reads.
278
+ */
279
+ declare const CONTEXT_REQUIREMENTS_KEY: "x-context-requirements";
280
+ /**
281
+ * The key used inside `_meta` to declare context requirements.
282
+ * This is the PRIMARY mechanism — the Context Platform reads `_meta.contextRequirements`.
270
283
  *
271
284
  * @example
272
285
  * ```typescript
273
- * import { CONTEXT_REQUIREMENTS_KEY } from "@ctxprotocol/sdk";
274
- *
275
286
  * const tool = {
276
287
  * name: "analyze_my_positions",
288
+ * _meta: {
289
+ * [META_CONTEXT_REQUIREMENTS_KEY]: ["hyperliquid"] as ContextRequirementType[],
290
+ * },
277
291
  * inputSchema: {
278
292
  * type: "object",
279
- * [CONTEXT_REQUIREMENTS_KEY]: ["hyperliquid"],
280
293
  * properties: { portfolio: { type: "object" } },
281
294
  * required: ["portfolio"]
282
295
  * }
283
296
  * };
284
297
  * ```
285
298
  */
286
- declare const CONTEXT_REQUIREMENTS_KEY: "x-context-requirements";
299
+ declare const META_CONTEXT_REQUIREMENTS_KEY: "contextRequirements";
287
300
  /**
288
301
  * Context requirement types supported by the Context marketplace.
289
302
  * Maps to protocol-specific context builders on the platform.
@@ -434,4 +447,245 @@ interface CreateContextMiddlewareOptions {
434
447
  */
435
448
  declare function createContextMiddleware(options?: CreateContextMiddlewareOptions): (req: ContextRequest, res: ContextResponse, next: NextFunction) => Promise<void>;
436
449
 
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 };
450
+ /**
451
+ * Handshake Types for MCP Tool Developers
452
+ *
453
+ * Use these types when your tool needs to request user interaction
454
+ * before completing an action (signatures, transactions, OAuth).
455
+ *
456
+ * @see https://docs.ctxprotocol.com/guides/handshake-architecture
457
+ *
458
+ * ## Usage Pattern
459
+ *
460
+ * Tools return handshake actions in the `_meta.handshakeAction` field
461
+ * of their MCP response. The Context platform intercepts these and
462
+ * presents the appropriate UI to the user.
463
+ *
464
+ * ## Action Types
465
+ *
466
+ * - `signature_request`: For EIP-712 signatures (Hyperliquid, Polymarket, etc.)
467
+ * - `transaction_proposal`: For direct on-chain transactions (Uniswap, NFT mints)
468
+ * - `auth_required`: For OAuth flows (Discord, Twitter, etc.)
469
+ */
470
+ type HandshakeMeta = {
471
+ /** Human-readable description of the action */
472
+ description: string;
473
+ /** Protocol name (e.g., "Hyperliquid", "Polymarket") */
474
+ protocol?: string;
475
+ /** Action verb (e.g., "Place Order", "Place Bid") */
476
+ action?: string;
477
+ /** Token symbol if relevant */
478
+ tokenSymbol?: string;
479
+ /** Human-readable token amount */
480
+ tokenAmount?: string;
481
+ /** UI warning level */
482
+ warningLevel?: "info" | "caution" | "danger";
483
+ /** Custom title for the signature card (marketplace-friendly, overrides action-based title) */
484
+ title?: string;
485
+ /** Custom subtitle for the signature card (overrides tool name display) */
486
+ subtitle?: string;
487
+ };
488
+ type EIP712Domain = {
489
+ /** Domain name (e.g., "Hyperliquid", "ClobAuthDomain") */
490
+ name: string;
491
+ /** Domain version */
492
+ version: string;
493
+ /** Chain ID (informational - signing is chain-agnostic) */
494
+ chainId: number;
495
+ /** Optional verifying contract address */
496
+ verifyingContract?: `0x${string}`;
497
+ };
498
+ type EIP712TypeField = {
499
+ name: string;
500
+ type: string;
501
+ };
502
+ /**
503
+ * Signature Request
504
+ *
505
+ * Use this for platforms with proxy wallets (Hyperliquid, Polymarket, dYdX).
506
+ *
507
+ * Benefits:
508
+ * - No gas required (user signs a message, not a transaction)
509
+ * - No network switching needed (signing is chain-agnostic)
510
+ * - Works with Privy embedded wallets on any chain
511
+ *
512
+ * @example
513
+ * ```typescript
514
+ * return {
515
+ * structuredContent: {
516
+ * _meta: {
517
+ * handshakeAction: createSignatureRequest({
518
+ * domain: { name: "Hyperliquid", version: "1", chainId: 42161 },
519
+ * types: { Order: [...] },
520
+ * primaryType: "Order",
521
+ * message: { asset: 4, isBuy: true, ... },
522
+ * meta: { description: "Place Long ETH order", protocol: "Hyperliquid" }
523
+ * })
524
+ * }
525
+ * }
526
+ * };
527
+ * ```
528
+ */
529
+ type SignatureRequest = {
530
+ _action: "signature_request";
531
+ /** EIP-712 domain separator */
532
+ domain: EIP712Domain;
533
+ /** EIP-712 type definitions */
534
+ types: Record<string, EIP712TypeField[]>;
535
+ /** The primary type being signed */
536
+ primaryType: string;
537
+ /** The message data to sign */
538
+ message: Record<string, unknown>;
539
+ /** UI metadata for the approval card */
540
+ meta?: HandshakeMeta;
541
+ /**
542
+ * Optional: Tool name to call with the signature result.
543
+ * If provided, the platform will call this tool with { signature, originalParams }
544
+ * after the user signs.
545
+ */
546
+ callbackToolName?: string;
547
+ };
548
+ type TransactionProposalMeta = HandshakeMeta & {
549
+ /** Estimated gas cost (informational - Context may sponsor) */
550
+ estimatedGas?: string;
551
+ /** Link to contract on block explorer */
552
+ explorerUrl?: string;
553
+ };
554
+ /**
555
+ * Transaction Proposal
556
+ *
557
+ * Use this for protocols without proxy wallets (Uniswap, NFT mints, etc.).
558
+ *
559
+ * Note: May require network switching and gas fees.
560
+ *
561
+ * @example
562
+ * ```typescript
563
+ * return {
564
+ * structuredContent: {
565
+ * _meta: {
566
+ * handshakeAction: createTransactionProposal({
567
+ * chainId: 8453,
568
+ * to: "0x...",
569
+ * data: "0x...",
570
+ * meta: { description: "Swap 100 USDC for ETH", protocol: "Uniswap" }
571
+ * })
572
+ * }
573
+ * }
574
+ * };
575
+ * ```
576
+ */
577
+ type TransactionProposal = {
578
+ _action: "transaction_proposal";
579
+ /** EVM chain ID (e.g., 137 for Polygon, 8453 for Base) */
580
+ chainId: number;
581
+ /** Target contract address */
582
+ to: `0x${string}`;
583
+ /** Encoded calldata */
584
+ data: `0x${string}`;
585
+ /** Wei to send (as string, default "0") */
586
+ value?: string;
587
+ /** UI metadata for the approval card */
588
+ meta?: TransactionProposalMeta;
589
+ };
590
+ type AuthRequiredMeta = {
591
+ /** Human-friendly service name */
592
+ displayName?: string;
593
+ /** Permissions being requested */
594
+ scopes?: string[];
595
+ /** Description of what access is needed */
596
+ description?: string;
597
+ /** Tool's icon URL */
598
+ iconUrl?: string;
599
+ /** How long authorization lasts */
600
+ expiresIn?: string;
601
+ };
602
+ /**
603
+ * Auth Required
604
+ *
605
+ * Use this when your tool needs the user to authenticate with an external service.
606
+ *
607
+ * @example
608
+ * ```typescript
609
+ * if (!hasUserToken(contextDid)) {
610
+ * return {
611
+ * structuredContent: {
612
+ * _meta: {
613
+ * handshakeAction: createAuthRequired({
614
+ * provider: "discord",
615
+ * authUrl: "https://your-server.com/oauth/discord",
616
+ * meta: { displayName: "Discord Bot", scopes: ["send_messages"] }
617
+ * })
618
+ * }
619
+ * }
620
+ * };
621
+ * }
622
+ * ```
623
+ */
624
+ type AuthRequired = {
625
+ _action: "auth_required";
626
+ /** Service identifier (e.g., "discord", "slack") */
627
+ provider: string;
628
+ /** Your OAuth initiation endpoint (MUST be HTTPS) */
629
+ authUrl: string;
630
+ /** UI metadata for the auth card */
631
+ meta?: AuthRequiredMeta;
632
+ };
633
+ type HandshakeAction = SignatureRequest | TransactionProposal | AuthRequired;
634
+ declare function isHandshakeAction(value: unknown): value is HandshakeAction;
635
+ declare function isSignatureRequest(value: unknown): value is SignatureRequest;
636
+ declare function isTransactionProposal(value: unknown): value is TransactionProposal;
637
+ declare function isAuthRequired(value: unknown): value is AuthRequired;
638
+ /**
639
+ * Create a signature request response.
640
+ * Return this from your tool when you need the user to sign EIP-712 typed data.
641
+ *
642
+ * Use this for platforms with proxy wallets (Hyperliquid, Polymarket, dYdX).
643
+ * Benefits: No gas required, no network switching needed.
644
+ */
645
+ declare function createSignatureRequest(params: Omit<SignatureRequest, "_action">): SignatureRequest;
646
+ /**
647
+ * Create a transaction proposal response.
648
+ * Return this from your tool when you need the user to sign a direct on-chain transaction.
649
+ *
650
+ * Use this for protocols that don't use proxy wallets (Uniswap, NFT mints, etc.).
651
+ * Note: May require network switching and gas.
652
+ */
653
+ declare function createTransactionProposal(params: Omit<TransactionProposal, "_action">): TransactionProposal;
654
+ /**
655
+ * Create an auth required response.
656
+ * Return this from your tool when you need the user to authenticate via OAuth.
657
+ */
658
+ declare function createAuthRequired(params: Omit<AuthRequired, "_action">): AuthRequired;
659
+ /**
660
+ * Wrap a handshake action in the proper MCP response format.
661
+ *
662
+ * MCP tools should return handshake actions in `_meta.handshakeAction` to prevent
663
+ * the MCP SDK from stripping unknown fields.
664
+ *
665
+ * @example
666
+ * ```typescript
667
+ * // In your tool handler:
668
+ * return wrapHandshakeResponse(createSignatureRequest({
669
+ * domain: { name: "Hyperliquid", version: "1", chainId: 42161 },
670
+ * types: { Order: [...] },
671
+ * primaryType: "Order",
672
+ * message: orderData,
673
+ * meta: { description: "Place order", protocol: "Hyperliquid" }
674
+ * }));
675
+ * ```
676
+ */
677
+ declare function wrapHandshakeResponse(action: HandshakeAction): {
678
+ content: Array<{
679
+ type: "text";
680
+ text: string;
681
+ }>;
682
+ structuredContent: {
683
+ _meta: {
684
+ handshakeAction: HandshakeAction;
685
+ };
686
+ status: string;
687
+ message: string;
688
+ };
689
+ };
690
+
691
+ 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, META_CONTEXT_REQUIREMENTS_KEY, 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
@@ -1,13 +1,14 @@
1
- import { importSPKI, jwtVerify } from 'jose';
1
+ import { jwtVerify, importSPKI } from 'jose';
2
2
 
3
3
  // src/client/types.ts
4
- var ContextError = class extends Error {
4
+ var ContextError = class _ContextError extends Error {
5
5
  constructor(message, code, statusCode, helpUrl) {
6
6
  super(message);
7
7
  this.code = code;
8
8
  this.statusCode = statusCode;
9
9
  this.helpUrl = helpUrl;
10
10
  this.name = "ContextError";
11
+ Object.setPrototypeOf(this, _ContextError.prototype);
11
12
  }
12
13
  };
13
14
 
@@ -40,7 +41,7 @@ var Discovery = class {
40
41
  }
41
42
  const queryString = params.toString();
42
43
  const endpoint = `/api/v1/tools/search${queryString ? `?${queryString}` : ""}`;
43
- const response = await this.client.fetch(endpoint);
44
+ const response = await this.client._fetch(endpoint);
44
45
  return response.tools;
45
46
  }
46
47
  /**
@@ -97,7 +98,7 @@ var Tools = class {
97
98
  */
98
99
  async execute(options) {
99
100
  const { toolId, toolName, args } = options;
100
- const response = await this.client.fetch(
101
+ const response = await this.client._fetch(
101
102
  "/api/v1/tools/execute",
102
103
  {
103
104
  method: "POST",
@@ -108,7 +109,8 @@ var Tools = class {
108
109
  throw new ContextError(
109
110
  response.error,
110
111
  response.code,
111
- 400,
112
+ void 0,
113
+ // Don't hardcode - this was a 200 OK with error body
112
114
  response.helpUrl
113
115
  );
114
116
  }
@@ -127,6 +129,7 @@ var Tools = class {
127
129
  var ContextClient = class {
128
130
  apiKey;
129
131
  baseUrl;
132
+ _closed = false;
130
133
  /**
131
134
  * Discovery resource for searching tools
132
135
  */
@@ -151,43 +154,95 @@ var ContextClient = class {
151
154
  this.discovery = new Discovery(this);
152
155
  this.tools = new Tools(this);
153
156
  }
157
+ /**
158
+ * Close the client and clean up resources.
159
+ * After calling close(), any in-flight requests may be aborted.
160
+ */
161
+ close() {
162
+ this._closed = true;
163
+ }
154
164
  /**
155
165
  * Internal method for making authenticated HTTP requests
156
- * All requests include the Authorization header with the API key
166
+ * Includes timeout (30s) and retry with exponential backoff for transient errors
157
167
  *
158
168
  * @internal
159
169
  */
160
- async fetch(endpoint, options = {}) {
170
+ async _fetch(endpoint, options = {}) {
171
+ if (this._closed) {
172
+ throw new ContextError("Client has been closed");
173
+ }
161
174
  const url = `${this.baseUrl}${endpoint}`;
162
- const response = await fetch(url, {
163
- ...options,
164
- headers: {
165
- "Content-Type": "application/json",
166
- Authorization: `Bearer ${this.apiKey}`,
167
- ...options.headers
168
- }
169
- });
170
- if (!response.ok) {
171
- let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
172
- let errorCode;
173
- let helpUrl;
175
+ const maxRetries = 3;
176
+ const timeoutMs = 3e4;
177
+ let lastError;
178
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
179
+ const controller = new AbortController();
180
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
174
181
  try {
175
- const errorBody = await response.json();
176
- if (errorBody.error) {
177
- errorMessage = errorBody.error;
178
- errorCode = errorBody.code;
179
- helpUrl = errorBody.helpUrl;
182
+ const response = await fetch(url, {
183
+ ...options,
184
+ signal: controller.signal,
185
+ headers: {
186
+ "Content-Type": "application/json",
187
+ Authorization: `Bearer ${this.apiKey}`,
188
+ ...options.headers
189
+ }
190
+ });
191
+ clearTimeout(timeout);
192
+ if (!response.ok) {
193
+ if (response.status >= 500 && attempt < maxRetries) {
194
+ const delay = Math.min(1e3 * 2 ** attempt, 1e4);
195
+ await new Promise((resolve) => setTimeout(resolve, delay));
196
+ continue;
197
+ }
198
+ let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
199
+ let errorCode;
200
+ let helpUrl;
201
+ try {
202
+ const errorBody = await response.json();
203
+ if (errorBody.error) {
204
+ errorMessage = errorBody.error;
205
+ errorCode = errorBody.code;
206
+ helpUrl = errorBody.helpUrl;
207
+ }
208
+ } catch {
209
+ }
210
+ throw new ContextError(errorMessage, errorCode, response.status, helpUrl);
211
+ }
212
+ return response.json();
213
+ } catch (error) {
214
+ clearTimeout(timeout);
215
+ if (error instanceof ContextError) {
216
+ throw error;
217
+ }
218
+ lastError = error instanceof Error ? error : new Error(String(error));
219
+ const isRetryable = lastError.name === "AbortError" || lastError.message.includes("fetch failed") || lastError.message.includes("ECONNRESET") || lastError.message.includes("ETIMEDOUT");
220
+ if (isRetryable && attempt < maxRetries) {
221
+ const delay = Math.min(1e3 * 2 ** attempt, 1e4);
222
+ await new Promise((resolve) => setTimeout(resolve, delay));
223
+ continue;
180
224
  }
181
- } catch {
225
+ if (lastError.name === "AbortError") {
226
+ throw new ContextError(
227
+ `Request timed out after ${timeoutMs / 1e3}s`,
228
+ void 0,
229
+ 408
230
+ );
231
+ }
232
+ throw new ContextError(
233
+ lastError.message,
234
+ void 0,
235
+ void 0
236
+ );
182
237
  }
183
- throw new ContextError(errorMessage, errorCode, response.status, helpUrl);
184
238
  }
185
- return response.json();
239
+ throw lastError ?? new ContextError("Request failed after retries");
186
240
  }
187
241
  };
188
242
 
189
243
  // src/context/index.ts
190
244
  var CONTEXT_REQUIREMENTS_KEY = "x-context-requirements";
245
+ var META_CONTEXT_REQUIREMENTS_KEY = "contextRequirements";
191
246
  var CONTEXT_PLATFORM_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
192
247
  MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs9YOgdpkmVQ5aoNovjsu
193
248
  chJdV54OT7dUdbVXz914a7Px8EwnpDqhsvG7WO8xL8sj2Rn6ueAJBk+04Hy/P/UN
@@ -197,6 +252,41 @@ lfOKbr7w0u72dZjiZPwnNDsX6PEEgvfmoautTFYTQgnZjDzq8UimTcv3KF+hJ5Ep
197
252
  weipe6amt9lzQzi8WXaFKpOXHQs//WDlUytz/Hl8pvd5craZKzo6Kyrg1Vfan7H3
198
253
  TQIDAQAB
199
254
  -----END PUBLIC KEY-----`;
255
+ var JWKS_URL = "https://ctxprotocol.com/.well-known/jwks.json";
256
+ var KEY_CACHE_TTL_MS = 36e5;
257
+ var cachedPublicKey = null;
258
+ var cacheTimestamp = 0;
259
+ async function getPlatformPublicKey() {
260
+ const now = Date.now();
261
+ if (cachedPublicKey && now - cacheTimestamp < KEY_CACHE_TTL_MS) {
262
+ return cachedPublicKey;
263
+ }
264
+ try {
265
+ const controller = new AbortController();
266
+ const timeout = setTimeout(() => controller.abort(), 5e3);
267
+ const response = await fetch(JWKS_URL, { signal: controller.signal });
268
+ clearTimeout(timeout);
269
+ if (response.ok) {
270
+ const jwks = await response.json();
271
+ if (jwks.keys && jwks.keys.length > 0) {
272
+ const key = jwks.keys[0];
273
+ if (key.x5c && key.x5c.length > 0) {
274
+ const pem = `-----BEGIN CERTIFICATE-----
275
+ ${key.x5c[0]}
276
+ -----END CERTIFICATE-----`;
277
+ const { importX509 } = await import('jose');
278
+ cachedPublicKey = await importX509(pem, "RS256");
279
+ cacheTimestamp = now;
280
+ return cachedPublicKey;
281
+ }
282
+ }
283
+ }
284
+ } catch {
285
+ }
286
+ cachedPublicKey = await importSPKI(CONTEXT_PLATFORM_PUBLIC_KEY_PEM, "RS256");
287
+ cacheTimestamp = now;
288
+ return cachedPublicKey;
289
+ }
200
290
  var PROTECTED_MCP_METHODS = /* @__PURE__ */ new Set([
201
291
  "tools/call"
202
292
  // Uncomment these if you want to protect resource/prompt access:
@@ -228,7 +318,7 @@ async function verifyContextRequest(options) {
228
318
  }
229
319
  const token = authorizationHeader.split(" ")[1];
230
320
  try {
231
- const publicKey = await importSPKI(CONTEXT_PLATFORM_PUBLIC_KEY_PEM, "RS256");
321
+ const publicKey = await getPlatformPublicKey();
232
322
  const { payload } = await jwtVerify(token, publicKey, {
233
323
  issuer: "https://ctxprotocol.com",
234
324
  audience
@@ -262,6 +352,56 @@ function createContextMiddleware(options = {}) {
262
352
  };
263
353
  }
264
354
 
265
- export { CONTEXT_REQUIREMENTS_KEY, ContextClient, ContextError, Discovery, Tools, createContextMiddleware, isOpenMcpMethod, isProtectedMcpMethod, verifyContextRequest };
355
+ // src/handshake/types.ts
356
+ function isHandshakeAction(value) {
357
+ return typeof value === "object" && value !== null && "_action" in value && (value._action === "signature_request" || value._action === "transaction_proposal" || value._action === "auth_required");
358
+ }
359
+ function isSignatureRequest(value) {
360
+ return isHandshakeAction(value) && value._action === "signature_request";
361
+ }
362
+ function isTransactionProposal(value) {
363
+ return isHandshakeAction(value) && value._action === "transaction_proposal";
364
+ }
365
+ function isAuthRequired(value) {
366
+ return isHandshakeAction(value) && value._action === "auth_required";
367
+ }
368
+ function createSignatureRequest(params) {
369
+ return {
370
+ _action: "signature_request",
371
+ ...params
372
+ };
373
+ }
374
+ function createTransactionProposal(params) {
375
+ return {
376
+ _action: "transaction_proposal",
377
+ ...params
378
+ };
379
+ }
380
+ function createAuthRequired(params) {
381
+ return {
382
+ _action: "auth_required",
383
+ ...params
384
+ };
385
+ }
386
+ function wrapHandshakeResponse(action) {
387
+ const actionType = action._action.replace("_", " ");
388
+ return {
389
+ content: [
390
+ {
391
+ type: "text",
392
+ text: `Handshake required: ${actionType}. Please approve in the Context app.`
393
+ }
394
+ ],
395
+ structuredContent: {
396
+ _meta: {
397
+ handshakeAction: action
398
+ },
399
+ status: "handshake_required",
400
+ message: action.meta?.description ?? `${actionType} required`
401
+ }
402
+ };
403
+ }
404
+
405
+ export { CONTEXT_REQUIREMENTS_KEY, ContextClient, ContextError, Discovery, META_CONTEXT_REQUIREMENTS_KEY, Tools, createAuthRequired, createContextMiddleware, createSignatureRequest, createTransactionProposal, isAuthRequired, isHandshakeAction, isOpenMcpMethod, isProtectedMcpMethod, isSignatureRequest, isTransactionProposal, verifyContextRequest, wrapHandshakeResponse };
266
406
  //# sourceMappingURL=index.js.map
267
407
  //# sourceMappingURL=index.js.map