@ctxprotocol/sdk 0.5.3 → 0.5.5

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
@@ -395,6 +395,42 @@ try {
395
395
 
396
396
  If you're building an MCP server (tool contributor), you should verify that incoming requests are legitimate and originate from the Context Protocol Platform.
397
397
 
398
+ ### The "Business in a Box" Promise
399
+
400
+ By adding 1 line of code to verify a JWT, Context saves you from building:
401
+ - A Stripe integration
402
+ - A User Management system
403
+ - API key management
404
+ - Refund and dispute logic
405
+
406
+ **The "Stripe Webhook" Analogy:**
407
+ Developers are used to verifying signatures for Stripe Webhooks or GitHub Apps. Context works the same way. When we send a request saying "Execute Tool (Payment Confirmed)", you verify the signature. Without this, anyone could curl your endpoint and drain your resources.
408
+
409
+ ### Quick Implementation (1 Line)
410
+
411
+ ```typescript
412
+ import express from "express";
413
+ import { createContextMiddleware } from "@ctxprotocol/sdk";
414
+
415
+ const app = express();
416
+ app.use(express.json());
417
+
418
+ // 1 line of code to secure your endpoint & handle payments
419
+ app.use("/mcp", createContextMiddleware());
420
+
421
+ app.post("/mcp", (req, res) => {
422
+ // req.context contains verified JWT payload (on protected methods)
423
+ // Handle MCP request...
424
+ });
425
+ ```
426
+
427
+ ### Free vs. Paid Strategy
428
+
429
+ | Tool Type | Security | Rationale |
430
+ |-----------|----------|-----------|
431
+ | **Free Tools ($0.00)** | Optional | Perfect for distribution and adoption |
432
+ | **Paid Tools ($0.01+)** | **Mandatory** | We cannot route payments to insecure endpoints |
433
+
398
434
  ### Security Model
399
435
 
400
436
  The SDK implements a **selective authentication** model:
@@ -413,9 +449,9 @@ This matches standard API patterns (OpenAPI schemas are public, GraphQL introspe
413
449
 
414
450
  The Context Platform signs **execution requests** (`tools/call`) using **RS256** (RSA-SHA256) asymmetric cryptography. Each request includes an `Authorization: Bearer <jwt>` header containing a signed JWT.
415
451
 
416
- ### Using `isProtectedMcpMethod` and `verifyContextRequest`
452
+ ### Advanced: Manual Verification
417
453
 
418
- The SDK provides utilities to implement this security model:
454
+ For more control, you can use the lower-level utilities:
419
455
 
420
456
  ```typescript
421
457
  import {
@@ -423,41 +459,15 @@ import {
423
459
  isProtectedMcpMethod,
424
460
  ContextError
425
461
  } from "@ctxprotocol/sdk";
426
- ```
427
-
428
- #### Express.js MCP Server Example
429
-
430
- ```typescript
431
- import express from "express";
432
- import { verifyContextRequest, isProtectedMcpMethod, ContextError } from "@ctxprotocol/sdk";
433
-
434
- const app = express();
435
- app.use(express.json());
436
462
 
437
- // Auth middleware - only protects tools/call, not tools/list
438
- async function verifyContextAuth(req, res, next) {
439
- const method = req.body?.method;
440
-
441
- // Only require auth for protected methods (tools/call)
442
- // Discovery methods (tools/list, initialize) are open
443
- if (!method || !isProtectedMcpMethod(method)) {
444
- return next();
445
- }
446
-
447
- try {
448
- await verifyContextRequest({
449
- authorizationHeader: req.headers.authorization,
450
- });
451
- next();
452
- } catch (err) {
453
- const statusCode = err instanceof ContextError ? err.statusCode || 401 : 401;
454
- res.status(statusCode).json({ error: "Unauthorized" });
455
- }
463
+ // Check if a method requires auth
464
+ if (isProtectedMcpMethod(body.method)) {
465
+ const payload = await verifyContextRequest({
466
+ authorizationHeader: req.headers.authorization,
467
+ audience: "https://your-tool.com/mcp", // optional
468
+ });
469
+ // payload contains verified JWT claims
456
470
  }
457
-
458
- app.post("/mcp", verifyContextAuth, async (req, res) => {
459
- // Handle MCP request...
460
- });
461
471
  ```
462
472
 
463
473
  ### Options
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"
@@ -238,12 +244,32 @@ async function verifyContextRequest(options) {
238
244
  );
239
245
  }
240
246
  }
247
+ function createContextMiddleware(options = {}) {
248
+ return async function contextMiddleware(req, res, next) {
249
+ const method = req.body?.method;
250
+ if (!method || !isProtectedMcpMethod(method)) {
251
+ return next();
252
+ }
253
+ try {
254
+ const payload = await verifyContextRequest({
255
+ authorizationHeader: req.headers.authorization,
256
+ audience: options.audience
257
+ });
258
+ req.context = payload;
259
+ next();
260
+ } catch (error) {
261
+ const statusCode = error instanceof ContextError ? error.statusCode || 401 : 401;
262
+ res.status(statusCode).json({ error: "Unauthorized" });
263
+ }
264
+ };
265
+ }
241
266
 
242
267
  exports.CONTEXT_REQUIREMENTS_KEY = CONTEXT_REQUIREMENTS_KEY;
243
268
  exports.ContextClient = ContextClient;
244
269
  exports.ContextError = ContextError;
245
270
  exports.Discovery = Discovery;
246
271
  exports.Tools = Tools;
272
+ exports.createContextMiddleware = createContextMiddleware;
247
273
  exports.isOpenMcpMethod = isOpenMcpMethod;
248
274
  exports.isProtectedMcpMethod = isProtectedMcpMethod;
249
275
  exports.verifyContextRequest = verifyContextRequest;
@@ -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 } from \"jose\";\nimport { ContextError } from \"../client/types.js\";\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 * 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\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"]}
1
+ {"version":3,"sources":["../src/client/types.ts","../src/client/resources/discovery.ts","../src/client/resources/tools.ts","../src/client/client.ts","../src/context/index.ts","../src/auth/index.ts"],"names":["importSPKI","jwtVerify"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Configuration options for initializing the ContextClient\n */\nexport interface ContextClientOptions {\n /**\n * Your Context Protocol API key\n * @example \"sk_live_abc123...\"\n */\n apiKey: string;\n\n /**\n * Base URL for the Context Protocol API\n * @default \"https://ctxprotocol.com\"\n */\n baseUrl?: string;\n}\n\n/**\n * An individual MCP tool exposed by a tool listing\n */\nexport interface McpTool {\n /** Name of the MCP tool method */\n name: string;\n\n /** Description of what this method does */\n description: string;\n\n /**\n * JSON Schema for the input arguments this tool accepts.\n * Used by LLMs to generate correct arguments.\n */\n inputSchema?: Record<string, unknown>;\n\n /**\n * JSON Schema for the output this tool returns.\n * Used by LLMs to understand the response structure.\n */\n outputSchema?: Record<string, unknown>;\n}\n\n/**\n * Represents a tool available on the Context Protocol marketplace\n */\nexport interface Tool {\n /** Unique identifier for the tool (UUID) */\n id: string;\n\n /** Human-readable name of the tool */\n name: string;\n\n /** Description of what the tool does */\n description: string;\n\n /** Price per execution in USDC */\n price: string;\n\n /** Tool category (e.g., \"defi\", \"nft\") */\n category?: string;\n\n /** Whether the tool is verified by Context Protocol */\n isVerified?: boolean;\n\n /**\n * Available MCP tool methods\n * Use items from this array as `toolName` when executing\n */\n mcpTools?: McpTool[];\n\n /** Creation timestamp */\n createdAt?: string;\n\n /** Last update timestamp */\n updatedAt?: string;\n}\n\n/**\n * Response from the tools search endpoint\n */\nexport interface SearchResponse {\n /** Array of matching tools */\n tools: Tool[];\n\n /** The search query that was used */\n query: string;\n\n /** Total number of results */\n count: number;\n}\n\n/**\n * Options for searching tools\n */\nexport interface SearchOptions {\n /** Search query (semantic search) */\n query?: string;\n\n /** Maximum number of results (1-50, default 10) */\n limit?: number;\n}\n\n/**\n * Options for executing a tool\n */\nexport interface ExecuteOptions {\n /** The UUID of the tool to execute (from search results) */\n toolId: string;\n\n /** The specific MCP tool name to call (from tool's mcpTools array) */\n toolName: string;\n\n /** Arguments to pass to the tool */\n args?: Record<string, unknown>;\n}\n\n/**\n * Successful execution response from the API\n */\nexport interface ExecuteApiSuccessResponse {\n success: true;\n\n /** The result data from the tool execution */\n result: unknown;\n\n /** Information about the executed tool */\n tool: {\n id: string;\n name: string;\n };\n\n /** Execution duration in milliseconds */\n durationMs: number;\n}\n\n/**\n * Error response from the API\n */\nexport interface ExecuteApiErrorResponse {\n /** Human-readable error message */\n error: string;\n\n /** Error code for programmatic handling */\n code?: ContextErrorCode;\n\n /** URL to help resolve the issue */\n helpUrl?: string;\n}\n\n/**\n * Raw API response from the execute endpoint\n */\nexport type ExecuteApiResponse = ExecuteApiSuccessResponse | ExecuteApiErrorResponse;\n\n/**\n * The resolved result returned to the user after SDK processing\n */\nexport interface ExecutionResult<T = unknown> {\n /** The data returned by the tool */\n result: T;\n\n /** Information about the executed tool */\n tool: {\n id: string;\n name: string;\n };\n\n /** Execution duration in milliseconds */\n durationMs: number;\n}\n\n/**\n * Specific error codes returned by the Context Protocol API\n */\nexport type ContextErrorCode =\n | \"unauthorized\"\n | \"no_wallet\"\n | \"insufficient_allowance\"\n | \"payment_failed\"\n | \"execution_failed\";\n\n/**\n * Error thrown by the Context Protocol client\n */\nexport class ContextError extends Error {\n constructor(\n message: string,\n public readonly code?: ContextErrorCode | string,\n public readonly statusCode?: number,\n public readonly helpUrl?: string\n ) {\n super(message);\n this.name = \"ContextError\";\n }\n}\n","import type { Tool, SearchResponse } from \"../types.js\";\nimport type { ContextClient } from \"../client.js\";\n\n/**\n * Discovery resource for searching and finding tools on the Context Protocol marketplace\n */\nexport class Discovery {\n constructor(private client: ContextClient) {}\n\n /**\n * Search for tools matching a query string\n *\n * @param query - The search query (e.g., \"gas prices\", \"nft metadata\")\n * @param limit - Maximum number of results (1-50, default 10)\n * @returns Array of matching tools\n *\n * @example\n * ```typescript\n * const tools = await client.discovery.search(\"gas prices\");\n * console.log(tools[0].name); // \"Gas Price Oracle\"\n * console.log(tools[0].mcpTools); // Available methods\n * ```\n */\n async search(query: string, limit?: number): Promise<Tool[]> {\n const params = new URLSearchParams();\n\n if (query) {\n params.set(\"q\", query);\n }\n\n if (limit !== undefined) {\n params.set(\"limit\", String(limit));\n }\n\n const queryString = params.toString();\n const endpoint = `/api/v1/tools/search${queryString ? `?${queryString}` : \"\"}`;\n\n const response = await this.client.fetch<SearchResponse>(endpoint);\n\n return response.tools;\n }\n\n /**\n * Get featured/popular tools (empty query search)\n *\n * @param limit - Maximum number of results (1-50, default 10)\n * @returns Array of featured tools\n *\n * @example\n * ```typescript\n * const featured = await client.discovery.getFeatured(5);\n * ```\n */\n async getFeatured(limit?: number): Promise<Tool[]> {\n return this.search(\"\", limit);\n }\n}\n","import type {\n ExecuteOptions,\n ExecuteApiResponse,\n ExecutionResult,\n} from \"../types.js\";\nimport { ContextError } from \"../types.js\";\nimport type { ContextClient } from \"../client.js\";\n\n/**\n * Tools resource for executing tools on the Context Protocol marketplace\n */\nexport class Tools {\n constructor(private client: ContextClient) {}\n\n /**\n * Execute a tool with the provided arguments\n *\n * @param options - Execution options\n * @param options.toolId - The UUID of the tool (from search results)\n * @param options.toolName - The specific MCP tool method to call (from tool's mcpTools array)\n * @param options.args - Arguments to pass to the tool\n * @returns The execution result with the tool's output data\n *\n * @throws {ContextError} With code `no_wallet` if wallet not set up\n * @throws {ContextError} With code `insufficient_allowance` if Auto Pay not enabled\n * @throws {ContextError} With code `payment_failed` if on-chain payment fails\n * @throws {ContextError} With code `execution_failed` if tool execution fails\n *\n * @example\n * ```typescript\n * // First, search for a tool\n * const tools = await client.discovery.search(\"gas prices\");\n * const tool = tools[0];\n *\n * // Execute a specific method from the tool's mcpTools\n * const result = await client.tools.execute({\n * toolId: tool.id,\n * toolName: tool.mcpTools[0].name, // e.g., \"get_gas_prices\"\n * args: { chainId: 1 }\n * });\n *\n * console.log(result.result); // The tool's output\n * console.log(result.durationMs); // Execution time\n * ```\n */\n async execute<T = unknown>(options: ExecuteOptions): Promise<ExecutionResult<T>> {\n const { toolId, toolName, args } = options;\n\n const response = await this.client.fetch<ExecuteApiResponse>(\n \"/api/v1/tools/execute\",\n {\n method: \"POST\",\n body: JSON.stringify({ toolId, toolName, args }),\n }\n );\n\n // Handle error response\n if (\"error\" in response) {\n throw new ContextError(\n response.error,\n response.code,\n 400,\n response.helpUrl\n );\n }\n\n // Handle success response\n if (response.success) {\n return {\n result: response.result as T,\n tool: response.tool,\n durationMs: response.durationMs,\n };\n }\n\n // Fallback - shouldn't reach here with valid API responses\n throw new ContextError(\"Unexpected response format from API\");\n }\n}\n","import type { ContextClientOptions } from \"./types.js\";\nimport { ContextError } from \"./types.js\";\nimport { Discovery } from \"./resources/discovery.js\";\nimport { Tools } from \"./resources/tools.js\";\n\n/**\n * The official TypeScript client for the Context Protocol.\n *\n * Use this client to discover and execute AI tools programmatically.\n *\n * @example\n * ```typescript\n * import { ContextClient } from \"@contextprotocol/client\";\n *\n * const client = new ContextClient({\n * apiKey: \"sk_live_...\"\n * });\n *\n * // Discover tools\n * const tools = await client.discovery.search(\"gas prices\");\n *\n * // Execute a tool method\n * const result = await client.tools.execute({\n * toolId: tools[0].id,\n * toolName: tools[0].mcpTools[0].name,\n * args: { chainId: 1 }\n * });\n * ```\n */\nexport class ContextClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n /**\n * Discovery resource for searching tools\n */\n public readonly discovery: Discovery;\n\n /**\n * Tools resource for executing tools\n */\n public readonly tools: Tools;\n\n /**\n * Creates a new Context Protocol client\n *\n * @param options - Client configuration options\n * @param options.apiKey - Your Context Protocol API key (format: sk_live_...)\n * @param options.baseUrl - Optional base URL override (defaults to https://ctxprotocol.com)\n */\n constructor(options: ContextClientOptions) {\n if (!options.apiKey) {\n throw new ContextError(\"API key is required\");\n }\n\n this.apiKey = options.apiKey;\n this.baseUrl = (options.baseUrl ?? \"https://ctxprotocol.com\").replace(/\\/$/, \"\");\n\n // Initialize resources\n this.discovery = new Discovery(this);\n this.tools = new Tools(this);\n }\n\n /**\n * Internal method for making authenticated HTTP requests\n * All requests include the Authorization header with the API key\n *\n * @internal\n */\n async fetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`;\n\n const response = await fetch(url, {\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n let errorMessage = `HTTP ${response.status}: ${response.statusText}`;\n let errorCode: string | undefined;\n let helpUrl: string | undefined;\n\n try {\n const errorBody = await response.json();\n if (errorBody.error) {\n errorMessage = errorBody.error;\n errorCode = errorBody.code;\n helpUrl = errorBody.helpUrl;\n }\n } catch {\n // Use default error message if JSON parsing fails\n }\n\n throw new ContextError(errorMessage, errorCode, response.status, helpUrl);\n }\n\n return response.json() as Promise<T>;\n }\n}\n","/**\n * Context types for portfolio and protocol data injection.\n *\n * These types allow MCP tools to receive personalized user context\n * (wallet addresses, positions, balances) for analysis.\n *\n * =============================================================================\n * DECLARING CONTEXT REQUIREMENTS\n * =============================================================================\n *\n * Since the MCP protocol only transmits standard fields (name, description,\n * inputSchema, outputSchema), context requirements MUST be embedded in the\n * inputSchema using the \"x-context-requirements\" JSON Schema extension.\n *\n * @example\n * ```typescript\n * import { CONTEXT_REQUIREMENTS_KEY, type ContextRequirementType } from \"@ctxprotocol/sdk\";\n * import type { HyperliquidContext } from \"@ctxprotocol/sdk\";\n *\n * const tool = {\n * name: \"analyze_my_positions\",\n * inputSchema: {\n * type: \"object\",\n * [CONTEXT_REQUIREMENTS_KEY]: [\"hyperliquid\"] as ContextRequirementType[],\n * properties: {\n * portfolio: { type: \"object\" }\n * },\n * required: [\"portfolio\"]\n * }\n * };\n *\n * // Your handler receives the injected context:\n * function handleAnalyzeMyPositions(args: { portfolio: HyperliquidContext }) {\n * const { perpPositions, accountSummary } = args.portfolio;\n * // ... analyze and return insights\n * }\n * ```\n *\n * @packageDocumentation\n */\n\n// Wallet context types\nexport * from \"./wallet.js\";\n\n// Protocol-specific context types\nexport * from \"./polymarket.js\";\nexport * from \"./hyperliquid.js\";\n\n// Re-import for composite type\nimport type { WalletContext, ERC20Context } from \"./wallet.js\";\nimport type { PolymarketContext } from \"./polymarket.js\";\nimport type { HyperliquidContext } from \"./hyperliquid.js\";\n\n// ============================================================================\n// CONTEXT REQUIREMENTS\n//\n// MCP tools that need user portfolio data MUST declare this in inputSchema.\n// The MCP protocol only transmits standard fields (name, description,\n// inputSchema, outputSchema). Custom fields get stripped by the MCP SDK.\n// ============================================================================\n\n/**\n * JSON Schema extension key for declaring context requirements.\n *\n * WHY THIS APPROACH?\n * - MCP protocol only transmits: name, description, inputSchema, outputSchema\n * - Custom fields like `requirements` get stripped by MCP SDK during transport\n * - JSON Schema allows custom \"x-\" prefixed extension properties\n * - inputSchema is preserved end-to-end through MCP transport\n *\n * @example\n * ```typescript\n * import { CONTEXT_REQUIREMENTS_KEY } from \"@ctxprotocol/sdk\";\n *\n * const tool = {\n * name: \"analyze_my_positions\",\n * inputSchema: {\n * type: \"object\",\n * [CONTEXT_REQUIREMENTS_KEY]: [\"hyperliquid\"],\n * properties: { portfolio: { type: \"object\" } },\n * required: [\"portfolio\"]\n * }\n * };\n * ```\n */\nexport const CONTEXT_REQUIREMENTS_KEY = \"x-context-requirements\" as const;\n\n/**\n * Context requirement types supported by the Context marketplace.\n * Maps to protocol-specific context builders on the platform.\n *\n * @example\n * ```typescript\n * inputSchema: {\n * type: \"object\",\n * \"x-context-requirements\": [\"hyperliquid\"] as ContextRequirementType[],\n * properties: { portfolio: { type: \"object\" } },\n * required: [\"portfolio\"]\n * }\n * ```\n */\nexport type ContextRequirementType = \"polymarket\" | \"hyperliquid\" | \"wallet\";\n\n/**\n * @deprecated The `requirements` field at tool level gets stripped by MCP SDK.\n * Use `x-context-requirements` inside `inputSchema` instead.\n *\n * @example\n * ```typescript\n * // ❌ OLD (doesn't work - stripped by MCP SDK)\n * { requirements: { context: [\"hyperliquid\"] } }\n *\n * // ✅ NEW (works - preserved through MCP transport)\n * { inputSchema: { \"x-context-requirements\": [\"hyperliquid\"], ... } }\n * ```\n */\nexport interface ToolRequirements {\n /**\n * @deprecated Use `x-context-requirements` in inputSchema instead.\n */\n context?: ContextRequirementType[];\n}\n\n/**\n * Composite context for tools that need multiple data sources.\n *\n * This is the unified structure that can be passed to MCP tools\n * to provide comprehensive user context.\n */\nexport interface UserContext {\n /** Base wallet information */\n wallet?: WalletContext;\n /** ERC20 token holdings */\n erc20?: ERC20Context;\n /** Polymarket positions and orders */\n polymarket?: PolymarketContext;\n /** Hyperliquid perpetual positions and account data */\n hyperliquid?: HyperliquidContext;\n // Future protocols:\n // aave?: AaveContext;\n}\n","import { jwtVerify, importSPKI, type JWTPayload } from \"jose\";\nimport { ContextError } from \"../client/types.js\";\n\n// ============================================================================\n// Express-compatible types (avoid requiring express as a dependency)\n// ============================================================================\n\ninterface ContextRequest {\n headers: {\n authorization?: string;\n [key: string]: string | string[] | undefined;\n };\n body?: {\n method?: string;\n [key: string]: unknown;\n };\n context?: JWTPayload;\n}\n\ninterface ContextResponse {\n status(code: number): ContextResponse;\n json(data: unknown): void;\n}\n\ntype NextFunction = (error?: unknown) => void;\n\n/**\n * Extended Request object with verified Context Protocol JWT payload.\n *\n * After `createContextMiddleware()` runs successfully on a protected method,\n * the `context` property contains the decoded JWT claims.\n */\nexport interface ContextMiddlewareRequest extends ContextRequest {\n /** The verified JWT payload from Context Protocol (available after auth) */\n context?: JWTPayload;\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n// The Context Protocol Public Key\n// In a real scenario, this might be fetched from a well-known URL or passed in config.\n// For now, we hardcode the Official Platform Public Key.\n// Official Context Protocol Platform Public Key (RS256)\nconst CONTEXT_PLATFORM_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs9YOgdpkmVQ5aoNovjsu\nchJdV54OT7dUdbVXz914a7Px8EwnpDqhsvG7WO8xL8sj2Rn6ueAJBk+04Hy/P/UN\nRJyp23XL5TsGmb4rbfg0ii0MiL2nbVXuqvAe3JSM2BOFZR5bpwIVIaa8aonfamUy\nVXGc7OosF90ThdKjm9cXlVM+kV6IgSWc1502X7M3abQqRcTU/rluVXnky0eiWDQa\nlfOKbr7w0u72dZjiZPwnNDsX6PEEgvfmoautTFYTQgnZjDzq8UimTcv3KF+hJ5Ep\nweipe6amt9lzQzi8WXaFKpOXHQs//WDlUytz/Hl8pvd5craZKzo6Kyrg1Vfan7H3\nTQIDAQAB\n-----END PUBLIC KEY-----`;\n\n/**\n * MCP methods that require authentication.\n * - tools/call: Executes tool logic, may cost money\n * - resources/read: Reads potentially sensitive data\n * - prompts/get: Gets prompt content\n */\nconst PROTECTED_MCP_METHODS = new Set([\n \"tools/call\",\n // Uncomment these if you want to protect resource/prompt access:\n // \"resources/read\",\n // \"prompts/get\",\n]);\n\n/**\n * MCP methods that are always open (no auth required).\n * These are discovery/listing operations that return metadata only.\n */\nconst OPEN_MCP_METHODS = new Set([\n \"initialize\",\n \"tools/list\",\n \"resources/list\",\n \"prompts/list\",\n \"ping\",\n \"notifications/initialized\",\n]);\n\n// ============================================================================\n// Method Classification\n// ============================================================================\n\n/**\n * Determines if a given MCP method requires authentication.\n *\n * Discovery methods (tools/list, resources/list, etc.) are open.\n * Execution methods (tools/call) require authentication.\n *\n * @param method The MCP JSON-RPC method (e.g., \"tools/list\", \"tools/call\")\n * @returns true if the method requires authentication\n *\n * @example\n * ```typescript\n * if (isProtectedMcpMethod(body.method)) {\n * await verifyContextRequest({ authorizationHeader: req.headers.authorization });\n * }\n * ```\n */\nexport function isProtectedMcpMethod(method: string): boolean {\n return PROTECTED_MCP_METHODS.has(method);\n}\n\n/**\n * Determines if a given MCP method is explicitly open (no auth).\n *\n * @param method The MCP JSON-RPC method\n * @returns true if the method is known to be open\n */\nexport function isOpenMcpMethod(method: string): boolean {\n return OPEN_MCP_METHODS.has(method);\n}\n\n// ============================================================================\n// Request Verification\n// ============================================================================\n\nexport interface VerifyRequestOptions {\n /** The full Authorization header string (e.g. \"Bearer eyJ...\") */\n authorizationHeader?: string;\n /** Expected Audience (your tool URL) for stricter validation */\n audience?: string;\n}\n\n/**\n * Verifies that an incoming request originated from the Context Protocol Platform.\n *\n * @param options Contains the Authorization header\n * @returns The decoded payload if valid\n * @throws ContextError if invalid\n */\nexport async function verifyContextRequest(options: VerifyRequestOptions) {\n const { authorizationHeader, audience } = options;\n\n if (!authorizationHeader || !authorizationHeader.startsWith(\"Bearer \")) {\n throw new ContextError(\n \"Missing or invalid Authorization header\",\n \"unauthorized\",\n 401\n );\n }\n\n const token = authorizationHeader.split(\" \")[1];\n\n try {\n const publicKey = await importSPKI(CONTEXT_PLATFORM_PUBLIC_KEY_PEM, \"RS256\");\n\n const { payload } = await jwtVerify(token, publicKey, {\n issuer: \"https://ctxprotocol.com\",\n audience: audience,\n });\n\n return payload;\n } catch (error) {\n throw new ContextError(\n \"Invalid Context Protocol signature\",\n \"unauthorized\",\n 401\n );\n }\n}\n\n// ============================================================================\n// Easy-Mode Middleware\n// ============================================================================\n\nexport interface CreateContextMiddlewareOptions {\n /** Expected Audience (your tool URL) for stricter validation */\n audience?: string;\n}\n\n/**\n * Creates an Express/Connect-compatible middleware that secures your MCP endpoint.\n *\n * This is the \"1 line of code\" solution to secure your MCP server.\n * It automatically:\n * - Allows discovery methods (tools/list, initialize) without authentication\n * - Requires and verifies JWT for execution methods (tools/call)\n * - Attaches the verified payload to `req.context` for downstream use\n *\n * @param options Optional configuration\n * @returns Express-compatible middleware function\n *\n * @example\n * ```typescript\n * import express from \"express\";\n * import { createContextMiddleware } from \"@ctxprotocol/sdk\";\n *\n * const app = express();\n * app.use(express.json());\n *\n * // 1 line to secure your endpoint\n * app.use(\"/mcp\", createContextMiddleware());\n *\n * app.post(\"/mcp\", (req, res) => {\n * // req.context contains verified JWT payload (on protected methods)\n * // Handle MCP request...\n * });\n * ```\n */\nexport function createContextMiddleware(options: CreateContextMiddlewareOptions = {}) {\n return async function contextMiddleware(\n req: ContextRequest,\n res: ContextResponse,\n next: NextFunction\n ): Promise<void> {\n const method = req.body?.method as string | undefined;\n\n // Allow discovery methods without authentication\n // Discovery methods (tools/list, initialize, etc.) are open by design\n if (!method || !isProtectedMcpMethod(method)) {\n return next();\n }\n\n // Protected method - require authentication\n try {\n const payload = await verifyContextRequest({\n authorizationHeader: req.headers.authorization,\n audience: options.audience,\n });\n\n // Attach verified payload to request for downstream handlers\n req.context = payload;\n next();\n } catch (error) {\n const statusCode = error instanceof ContextError ? error.statusCode || 401 : 401;\n res.status(statusCode).json({ error: \"Unauthorized\" });\n }\n };\n}\n\n"]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { ContextClient, ContextClientOptions, ContextError, ContextErrorCode, Discovery, ExecuteApiErrorResponse, ExecuteApiResponse, ExecuteApiSuccessResponse, ExecuteOptions, ExecutionResult, McpTool, SearchOptions, SearchResponse, Tool, Tools } from './client/index.cjs';
2
- import * as jose from 'jose';
2
+ import { JWTPayload } from 'jose';
3
3
 
4
4
  /**
5
5
  * Wallet context types for portfolio tracking.
@@ -335,6 +335,32 @@ interface UserContext {
335
335
  hyperliquid?: HyperliquidContext;
336
336
  }
337
337
 
338
+ interface ContextRequest {
339
+ headers: {
340
+ authorization?: string;
341
+ [key: string]: string | string[] | undefined;
342
+ };
343
+ body?: {
344
+ method?: string;
345
+ [key: string]: unknown;
346
+ };
347
+ context?: JWTPayload;
348
+ }
349
+ interface ContextResponse {
350
+ status(code: number): ContextResponse;
351
+ json(data: unknown): void;
352
+ }
353
+ type NextFunction = (error?: unknown) => void;
354
+ /**
355
+ * Extended Request object with verified Context Protocol JWT payload.
356
+ *
357
+ * After `createContextMiddleware()` runs successfully on a protected method,
358
+ * the `context` property contains the decoded JWT claims.
359
+ */
360
+ interface ContextMiddlewareRequest extends ContextRequest {
361
+ /** The verified JWT payload from Context Protocol (available after auth) */
362
+ context?: JWTPayload;
363
+ }
338
364
  /**
339
365
  * Determines if a given MCP method requires authentication.
340
366
  *
@@ -372,6 +398,40 @@ interface VerifyRequestOptions {
372
398
  * @returns The decoded payload if valid
373
399
  * @throws ContextError if invalid
374
400
  */
375
- declare function verifyContextRequest(options: VerifyRequestOptions): Promise<jose.JWTPayload>;
401
+ declare function verifyContextRequest(options: VerifyRequestOptions): Promise<JWTPayload>;
402
+ interface CreateContextMiddlewareOptions {
403
+ /** Expected Audience (your tool URL) for stricter validation */
404
+ audience?: string;
405
+ }
406
+ /**
407
+ * Creates an Express/Connect-compatible middleware that secures your MCP endpoint.
408
+ *
409
+ * This is the "1 line of code" solution to secure your MCP server.
410
+ * It automatically:
411
+ * - Allows discovery methods (tools/list, initialize) without authentication
412
+ * - Requires and verifies JWT for execution methods (tools/call)
413
+ * - Attaches the verified payload to `req.context` for downstream use
414
+ *
415
+ * @param options Optional configuration
416
+ * @returns Express-compatible middleware function
417
+ *
418
+ * @example
419
+ * ```typescript
420
+ * import express from "express";
421
+ * import { createContextMiddleware } from "@ctxprotocol/sdk";
422
+ *
423
+ * const app = express();
424
+ * app.use(express.json());
425
+ *
426
+ * // 1 line to secure your endpoint
427
+ * app.use("/mcp", createContextMiddleware());
428
+ *
429
+ * app.post("/mcp", (req, res) => {
430
+ * // req.context contains verified JWT payload (on protected methods)
431
+ * // Handle MCP request...
432
+ * });
433
+ * ```
434
+ */
435
+ declare function createContextMiddleware(options?: CreateContextMiddlewareOptions): (req: ContextRequest, res: ContextResponse, next: NextFunction) => Promise<void>;
376
436
 
377
- export { CONTEXT_REQUIREMENTS_KEY, type ContextRequirementType, 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, isOpenMcpMethod, isProtectedMcpMethod, verifyContextRequest };
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 };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { ContextClient, ContextClientOptions, ContextError, ContextErrorCode, Discovery, ExecuteApiErrorResponse, ExecuteApiResponse, ExecuteApiSuccessResponse, ExecuteOptions, ExecutionResult, McpTool, SearchOptions, SearchResponse, Tool, Tools } from './client/index.js';
2
- import * as jose from 'jose';
2
+ import { JWTPayload } from 'jose';
3
3
 
4
4
  /**
5
5
  * Wallet context types for portfolio tracking.
@@ -335,6 +335,32 @@ interface UserContext {
335
335
  hyperliquid?: HyperliquidContext;
336
336
  }
337
337
 
338
+ interface ContextRequest {
339
+ headers: {
340
+ authorization?: string;
341
+ [key: string]: string | string[] | undefined;
342
+ };
343
+ body?: {
344
+ method?: string;
345
+ [key: string]: unknown;
346
+ };
347
+ context?: JWTPayload;
348
+ }
349
+ interface ContextResponse {
350
+ status(code: number): ContextResponse;
351
+ json(data: unknown): void;
352
+ }
353
+ type NextFunction = (error?: unknown) => void;
354
+ /**
355
+ * Extended Request object with verified Context Protocol JWT payload.
356
+ *
357
+ * After `createContextMiddleware()` runs successfully on a protected method,
358
+ * the `context` property contains the decoded JWT claims.
359
+ */
360
+ interface ContextMiddlewareRequest extends ContextRequest {
361
+ /** The verified JWT payload from Context Protocol (available after auth) */
362
+ context?: JWTPayload;
363
+ }
338
364
  /**
339
365
  * Determines if a given MCP method requires authentication.
340
366
  *
@@ -372,6 +398,40 @@ interface VerifyRequestOptions {
372
398
  * @returns The decoded payload if valid
373
399
  * @throws ContextError if invalid
374
400
  */
375
- declare function verifyContextRequest(options: VerifyRequestOptions): Promise<jose.JWTPayload>;
401
+ declare function verifyContextRequest(options: VerifyRequestOptions): Promise<JWTPayload>;
402
+ interface CreateContextMiddlewareOptions {
403
+ /** Expected Audience (your tool URL) for stricter validation */
404
+ audience?: string;
405
+ }
406
+ /**
407
+ * Creates an Express/Connect-compatible middleware that secures your MCP endpoint.
408
+ *
409
+ * This is the "1 line of code" solution to secure your MCP server.
410
+ * It automatically:
411
+ * - Allows discovery methods (tools/list, initialize) without authentication
412
+ * - Requires and verifies JWT for execution methods (tools/call)
413
+ * - Attaches the verified payload to `req.context` for downstream use
414
+ *
415
+ * @param options Optional configuration
416
+ * @returns Express-compatible middleware function
417
+ *
418
+ * @example
419
+ * ```typescript
420
+ * import express from "express";
421
+ * import { createContextMiddleware } from "@ctxprotocol/sdk";
422
+ *
423
+ * const app = express();
424
+ * app.use(express.json());
425
+ *
426
+ * // 1 line to secure your endpoint
427
+ * app.use("/mcp", createContextMiddleware());
428
+ *
429
+ * app.post("/mcp", (req, res) => {
430
+ * // req.context contains verified JWT payload (on protected methods)
431
+ * // Handle MCP request...
432
+ * });
433
+ * ```
434
+ */
435
+ declare function createContextMiddleware(options?: CreateContextMiddlewareOptions): (req: ContextRequest, res: ContextResponse, next: NextFunction) => Promise<void>;
376
436
 
377
- export { CONTEXT_REQUIREMENTS_KEY, type ContextRequirementType, 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, isOpenMcpMethod, isProtectedMcpMethod, verifyContextRequest };
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 };
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"
@@ -236,7 +242,26 @@ async function verifyContextRequest(options) {
236
242
  );
237
243
  }
238
244
  }
245
+ function createContextMiddleware(options = {}) {
246
+ return async function contextMiddleware(req, res, next) {
247
+ const method = req.body?.method;
248
+ if (!method || !isProtectedMcpMethod(method)) {
249
+ return next();
250
+ }
251
+ try {
252
+ const payload = await verifyContextRequest({
253
+ authorizationHeader: req.headers.authorization,
254
+ audience: options.audience
255
+ });
256
+ req.context = payload;
257
+ next();
258
+ } catch (error) {
259
+ const statusCode = error instanceof ContextError ? error.statusCode || 401 : 401;
260
+ res.status(statusCode).json({ error: "Unauthorized" });
261
+ }
262
+ };
263
+ }
239
264
 
240
- export { CONTEXT_REQUIREMENTS_KEY, ContextClient, ContextError, Discovery, Tools, isOpenMcpMethod, isProtectedMcpMethod, verifyContextRequest };
265
+ export { CONTEXT_REQUIREMENTS_KEY, ContextClient, ContextError, Discovery, Tools, createContextMiddleware, isOpenMcpMethod, isProtectedMcpMethod, verifyContextRequest };
241
266
  //# sourceMappingURL=index.js.map
242
267
  //# 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 } from \"jose\";\nimport { ContextError } from \"../client/types.js\";\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 * 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\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"]}
1
+ {"version":3,"sources":["../src/client/types.ts","../src/client/resources/discovery.ts","../src/client/resources/tools.ts","../src/client/client.ts","../src/context/index.ts","../src/auth/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Configuration options for initializing the ContextClient\n */\nexport interface ContextClientOptions {\n /**\n * Your Context Protocol API key\n * @example \"sk_live_abc123...\"\n */\n apiKey: string;\n\n /**\n * Base URL for the Context Protocol API\n * @default \"https://ctxprotocol.com\"\n */\n baseUrl?: string;\n}\n\n/**\n * An individual MCP tool exposed by a tool listing\n */\nexport interface McpTool {\n /** Name of the MCP tool method */\n name: string;\n\n /** Description of what this method does */\n description: string;\n\n /**\n * JSON Schema for the input arguments this tool accepts.\n * Used by LLMs to generate correct arguments.\n */\n inputSchema?: Record<string, unknown>;\n\n /**\n * JSON Schema for the output this tool returns.\n * Used by LLMs to understand the response structure.\n */\n outputSchema?: Record<string, unknown>;\n}\n\n/**\n * Represents a tool available on the Context Protocol marketplace\n */\nexport interface Tool {\n /** Unique identifier for the tool (UUID) */\n id: string;\n\n /** Human-readable name of the tool */\n name: string;\n\n /** Description of what the tool does */\n description: string;\n\n /** Price per execution in USDC */\n price: string;\n\n /** Tool category (e.g., \"defi\", \"nft\") */\n category?: string;\n\n /** Whether the tool is verified by Context Protocol */\n isVerified?: boolean;\n\n /**\n * Available MCP tool methods\n * Use items from this array as `toolName` when executing\n */\n mcpTools?: McpTool[];\n\n /** Creation timestamp */\n createdAt?: string;\n\n /** Last update timestamp */\n updatedAt?: string;\n}\n\n/**\n * Response from the tools search endpoint\n */\nexport interface SearchResponse {\n /** Array of matching tools */\n tools: Tool[];\n\n /** The search query that was used */\n query: string;\n\n /** Total number of results */\n count: number;\n}\n\n/**\n * Options for searching tools\n */\nexport interface SearchOptions {\n /** Search query (semantic search) */\n query?: string;\n\n /** Maximum number of results (1-50, default 10) */\n limit?: number;\n}\n\n/**\n * Options for executing a tool\n */\nexport interface ExecuteOptions {\n /** The UUID of the tool to execute (from search results) */\n toolId: string;\n\n /** The specific MCP tool name to call (from tool's mcpTools array) */\n toolName: string;\n\n /** Arguments to pass to the tool */\n args?: Record<string, unknown>;\n}\n\n/**\n * Successful execution response from the API\n */\nexport interface ExecuteApiSuccessResponse {\n success: true;\n\n /** The result data from the tool execution */\n result: unknown;\n\n /** Information about the executed tool */\n tool: {\n id: string;\n name: string;\n };\n\n /** Execution duration in milliseconds */\n durationMs: number;\n}\n\n/**\n * Error response from the API\n */\nexport interface ExecuteApiErrorResponse {\n /** Human-readable error message */\n error: string;\n\n /** Error code for programmatic handling */\n code?: ContextErrorCode;\n\n /** URL to help resolve the issue */\n helpUrl?: string;\n}\n\n/**\n * Raw API response from the execute endpoint\n */\nexport type ExecuteApiResponse = ExecuteApiSuccessResponse | ExecuteApiErrorResponse;\n\n/**\n * The resolved result returned to the user after SDK processing\n */\nexport interface ExecutionResult<T = unknown> {\n /** The data returned by the tool */\n result: T;\n\n /** Information about the executed tool */\n tool: {\n id: string;\n name: string;\n };\n\n /** Execution duration in milliseconds */\n durationMs: number;\n}\n\n/**\n * Specific error codes returned by the Context Protocol API\n */\nexport type ContextErrorCode =\n | \"unauthorized\"\n | \"no_wallet\"\n | \"insufficient_allowance\"\n | \"payment_failed\"\n | \"execution_failed\";\n\n/**\n * Error thrown by the Context Protocol client\n */\nexport class ContextError extends Error {\n constructor(\n message: string,\n public readonly code?: ContextErrorCode | string,\n public readonly statusCode?: number,\n public readonly helpUrl?: string\n ) {\n super(message);\n this.name = \"ContextError\";\n }\n}\n","import type { Tool, SearchResponse } from \"../types.js\";\nimport type { ContextClient } from \"../client.js\";\n\n/**\n * Discovery resource for searching and finding tools on the Context Protocol marketplace\n */\nexport class Discovery {\n constructor(private client: ContextClient) {}\n\n /**\n * Search for tools matching a query string\n *\n * @param query - The search query (e.g., \"gas prices\", \"nft metadata\")\n * @param limit - Maximum number of results (1-50, default 10)\n * @returns Array of matching tools\n *\n * @example\n * ```typescript\n * const tools = await client.discovery.search(\"gas prices\");\n * console.log(tools[0].name); // \"Gas Price Oracle\"\n * console.log(tools[0].mcpTools); // Available methods\n * ```\n */\n async search(query: string, limit?: number): Promise<Tool[]> {\n const params = new URLSearchParams();\n\n if (query) {\n params.set(\"q\", query);\n }\n\n if (limit !== undefined) {\n params.set(\"limit\", String(limit));\n }\n\n const queryString = params.toString();\n const endpoint = `/api/v1/tools/search${queryString ? `?${queryString}` : \"\"}`;\n\n const response = await this.client.fetch<SearchResponse>(endpoint);\n\n return response.tools;\n }\n\n /**\n * Get featured/popular tools (empty query search)\n *\n * @param limit - Maximum number of results (1-50, default 10)\n * @returns Array of featured tools\n *\n * @example\n * ```typescript\n * const featured = await client.discovery.getFeatured(5);\n * ```\n */\n async getFeatured(limit?: number): Promise<Tool[]> {\n return this.search(\"\", limit);\n }\n}\n","import type {\n ExecuteOptions,\n ExecuteApiResponse,\n ExecutionResult,\n} from \"../types.js\";\nimport { ContextError } from \"../types.js\";\nimport type { ContextClient } from \"../client.js\";\n\n/**\n * Tools resource for executing tools on the Context Protocol marketplace\n */\nexport class Tools {\n constructor(private client: ContextClient) {}\n\n /**\n * Execute a tool with the provided arguments\n *\n * @param options - Execution options\n * @param options.toolId - The UUID of the tool (from search results)\n * @param options.toolName - The specific MCP tool method to call (from tool's mcpTools array)\n * @param options.args - Arguments to pass to the tool\n * @returns The execution result with the tool's output data\n *\n * @throws {ContextError} With code `no_wallet` if wallet not set up\n * @throws {ContextError} With code `insufficient_allowance` if Auto Pay not enabled\n * @throws {ContextError} With code `payment_failed` if on-chain payment fails\n * @throws {ContextError} With code `execution_failed` if tool execution fails\n *\n * @example\n * ```typescript\n * // First, search for a tool\n * const tools = await client.discovery.search(\"gas prices\");\n * const tool = tools[0];\n *\n * // Execute a specific method from the tool's mcpTools\n * const result = await client.tools.execute({\n * toolId: tool.id,\n * toolName: tool.mcpTools[0].name, // e.g., \"get_gas_prices\"\n * args: { chainId: 1 }\n * });\n *\n * console.log(result.result); // The tool's output\n * console.log(result.durationMs); // Execution time\n * ```\n */\n async execute<T = unknown>(options: ExecuteOptions): Promise<ExecutionResult<T>> {\n const { toolId, toolName, args } = options;\n\n const response = await this.client.fetch<ExecuteApiResponse>(\n \"/api/v1/tools/execute\",\n {\n method: \"POST\",\n body: JSON.stringify({ toolId, toolName, args }),\n }\n );\n\n // Handle error response\n if (\"error\" in response) {\n throw new ContextError(\n response.error,\n response.code,\n 400,\n response.helpUrl\n );\n }\n\n // Handle success response\n if (response.success) {\n return {\n result: response.result as T,\n tool: response.tool,\n durationMs: response.durationMs,\n };\n }\n\n // Fallback - shouldn't reach here with valid API responses\n throw new ContextError(\"Unexpected response format from API\");\n }\n}\n","import type { ContextClientOptions } from \"./types.js\";\nimport { ContextError } from \"./types.js\";\nimport { Discovery } from \"./resources/discovery.js\";\nimport { Tools } from \"./resources/tools.js\";\n\n/**\n * The official TypeScript client for the Context Protocol.\n *\n * Use this client to discover and execute AI tools programmatically.\n *\n * @example\n * ```typescript\n * import { ContextClient } from \"@contextprotocol/client\";\n *\n * const client = new ContextClient({\n * apiKey: \"sk_live_...\"\n * });\n *\n * // Discover tools\n * const tools = await client.discovery.search(\"gas prices\");\n *\n * // Execute a tool method\n * const result = await client.tools.execute({\n * toolId: tools[0].id,\n * toolName: tools[0].mcpTools[0].name,\n * args: { chainId: 1 }\n * });\n * ```\n */\nexport class ContextClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n /**\n * Discovery resource for searching tools\n */\n public readonly discovery: Discovery;\n\n /**\n * Tools resource for executing tools\n */\n public readonly tools: Tools;\n\n /**\n * Creates a new Context Protocol client\n *\n * @param options - Client configuration options\n * @param options.apiKey - Your Context Protocol API key (format: sk_live_...)\n * @param options.baseUrl - Optional base URL override (defaults to https://ctxprotocol.com)\n */\n constructor(options: ContextClientOptions) {\n if (!options.apiKey) {\n throw new ContextError(\"API key is required\");\n }\n\n this.apiKey = options.apiKey;\n this.baseUrl = (options.baseUrl ?? \"https://ctxprotocol.com\").replace(/\\/$/, \"\");\n\n // Initialize resources\n this.discovery = new Discovery(this);\n this.tools = new Tools(this);\n }\n\n /**\n * Internal method for making authenticated HTTP requests\n * All requests include the Authorization header with the API key\n *\n * @internal\n */\n async fetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`;\n\n const response = await fetch(url, {\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n let errorMessage = `HTTP ${response.status}: ${response.statusText}`;\n let errorCode: string | undefined;\n let helpUrl: string | undefined;\n\n try {\n const errorBody = await response.json();\n if (errorBody.error) {\n errorMessage = errorBody.error;\n errorCode = errorBody.code;\n helpUrl = errorBody.helpUrl;\n }\n } catch {\n // Use default error message if JSON parsing fails\n }\n\n throw new ContextError(errorMessage, errorCode, response.status, helpUrl);\n }\n\n return response.json() as Promise<T>;\n }\n}\n","/**\n * Context types for portfolio and protocol data injection.\n *\n * These types allow MCP tools to receive personalized user context\n * (wallet addresses, positions, balances) for analysis.\n *\n * =============================================================================\n * DECLARING CONTEXT REQUIREMENTS\n * =============================================================================\n *\n * Since the MCP protocol only transmits standard fields (name, description,\n * inputSchema, outputSchema), context requirements MUST be embedded in the\n * inputSchema using the \"x-context-requirements\" JSON Schema extension.\n *\n * @example\n * ```typescript\n * import { CONTEXT_REQUIREMENTS_KEY, type ContextRequirementType } from \"@ctxprotocol/sdk\";\n * import type { HyperliquidContext } from \"@ctxprotocol/sdk\";\n *\n * const tool = {\n * name: \"analyze_my_positions\",\n * inputSchema: {\n * type: \"object\",\n * [CONTEXT_REQUIREMENTS_KEY]: [\"hyperliquid\"] as ContextRequirementType[],\n * properties: {\n * portfolio: { type: \"object\" }\n * },\n * required: [\"portfolio\"]\n * }\n * };\n *\n * // Your handler receives the injected context:\n * function handleAnalyzeMyPositions(args: { portfolio: HyperliquidContext }) {\n * const { perpPositions, accountSummary } = args.portfolio;\n * // ... analyze and return insights\n * }\n * ```\n *\n * @packageDocumentation\n */\n\n// Wallet context types\nexport * from \"./wallet.js\";\n\n// Protocol-specific context types\nexport * from \"./polymarket.js\";\nexport * from \"./hyperliquid.js\";\n\n// Re-import for composite type\nimport type { WalletContext, ERC20Context } from \"./wallet.js\";\nimport type { PolymarketContext } from \"./polymarket.js\";\nimport type { HyperliquidContext } from \"./hyperliquid.js\";\n\n// ============================================================================\n// CONTEXT REQUIREMENTS\n//\n// MCP tools that need user portfolio data MUST declare this in inputSchema.\n// The MCP protocol only transmits standard fields (name, description,\n// inputSchema, outputSchema). Custom fields get stripped by the MCP SDK.\n// ============================================================================\n\n/**\n * JSON Schema extension key for declaring context requirements.\n *\n * WHY THIS APPROACH?\n * - MCP protocol only transmits: name, description, inputSchema, outputSchema\n * - Custom fields like `requirements` get stripped by MCP SDK during transport\n * - JSON Schema allows custom \"x-\" prefixed extension properties\n * - inputSchema is preserved end-to-end through MCP transport\n *\n * @example\n * ```typescript\n * import { CONTEXT_REQUIREMENTS_KEY } from \"@ctxprotocol/sdk\";\n *\n * const tool = {\n * name: \"analyze_my_positions\",\n * inputSchema: {\n * type: \"object\",\n * [CONTEXT_REQUIREMENTS_KEY]: [\"hyperliquid\"],\n * properties: { portfolio: { type: \"object\" } },\n * required: [\"portfolio\"]\n * }\n * };\n * ```\n */\nexport const CONTEXT_REQUIREMENTS_KEY = \"x-context-requirements\" as const;\n\n/**\n * Context requirement types supported by the Context marketplace.\n * Maps to protocol-specific context builders on the platform.\n *\n * @example\n * ```typescript\n * inputSchema: {\n * type: \"object\",\n * \"x-context-requirements\": [\"hyperliquid\"] as ContextRequirementType[],\n * properties: { portfolio: { type: \"object\" } },\n * required: [\"portfolio\"]\n * }\n * ```\n */\nexport type ContextRequirementType = \"polymarket\" | \"hyperliquid\" | \"wallet\";\n\n/**\n * @deprecated The `requirements` field at tool level gets stripped by MCP SDK.\n * Use `x-context-requirements` inside `inputSchema` instead.\n *\n * @example\n * ```typescript\n * // ❌ OLD (doesn't work - stripped by MCP SDK)\n * { requirements: { context: [\"hyperliquid\"] } }\n *\n * // ✅ NEW (works - preserved through MCP transport)\n * { inputSchema: { \"x-context-requirements\": [\"hyperliquid\"], ... } }\n * ```\n */\nexport interface ToolRequirements {\n /**\n * @deprecated Use `x-context-requirements` in inputSchema instead.\n */\n context?: ContextRequirementType[];\n}\n\n/**\n * Composite context for tools that need multiple data sources.\n *\n * This is the unified structure that can be passed to MCP tools\n * to provide comprehensive user context.\n */\nexport interface UserContext {\n /** Base wallet information */\n wallet?: WalletContext;\n /** ERC20 token holdings */\n erc20?: ERC20Context;\n /** Polymarket positions and orders */\n polymarket?: PolymarketContext;\n /** Hyperliquid perpetual positions and account data */\n hyperliquid?: HyperliquidContext;\n // Future protocols:\n // aave?: AaveContext;\n}\n","import { jwtVerify, importSPKI, type JWTPayload } from \"jose\";\nimport { ContextError } from \"../client/types.js\";\n\n// ============================================================================\n// Express-compatible types (avoid requiring express as a dependency)\n// ============================================================================\n\ninterface ContextRequest {\n headers: {\n authorization?: string;\n [key: string]: string | string[] | undefined;\n };\n body?: {\n method?: string;\n [key: string]: unknown;\n };\n context?: JWTPayload;\n}\n\ninterface ContextResponse {\n status(code: number): ContextResponse;\n json(data: unknown): void;\n}\n\ntype NextFunction = (error?: unknown) => void;\n\n/**\n * Extended Request object with verified Context Protocol JWT payload.\n *\n * After `createContextMiddleware()` runs successfully on a protected method,\n * the `context` property contains the decoded JWT claims.\n */\nexport interface ContextMiddlewareRequest extends ContextRequest {\n /** The verified JWT payload from Context Protocol (available after auth) */\n context?: JWTPayload;\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n// The Context Protocol Public Key\n// In a real scenario, this might be fetched from a well-known URL or passed in config.\n// For now, we hardcode the Official Platform Public Key.\n// Official Context Protocol Platform Public Key (RS256)\nconst CONTEXT_PLATFORM_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs9YOgdpkmVQ5aoNovjsu\nchJdV54OT7dUdbVXz914a7Px8EwnpDqhsvG7WO8xL8sj2Rn6ueAJBk+04Hy/P/UN\nRJyp23XL5TsGmb4rbfg0ii0MiL2nbVXuqvAe3JSM2BOFZR5bpwIVIaa8aonfamUy\nVXGc7OosF90ThdKjm9cXlVM+kV6IgSWc1502X7M3abQqRcTU/rluVXnky0eiWDQa\nlfOKbr7w0u72dZjiZPwnNDsX6PEEgvfmoautTFYTQgnZjDzq8UimTcv3KF+hJ5Ep\nweipe6amt9lzQzi8WXaFKpOXHQs//WDlUytz/Hl8pvd5craZKzo6Kyrg1Vfan7H3\nTQIDAQAB\n-----END PUBLIC KEY-----`;\n\n/**\n * MCP methods that require authentication.\n * - tools/call: Executes tool logic, may cost money\n * - resources/read: Reads potentially sensitive data\n * - prompts/get: Gets prompt content\n */\nconst PROTECTED_MCP_METHODS = new Set([\n \"tools/call\",\n // Uncomment these if you want to protect resource/prompt access:\n // \"resources/read\",\n // \"prompts/get\",\n]);\n\n/**\n * MCP methods that are always open (no auth required).\n * These are discovery/listing operations that return metadata only.\n */\nconst OPEN_MCP_METHODS = new Set([\n \"initialize\",\n \"tools/list\",\n \"resources/list\",\n \"prompts/list\",\n \"ping\",\n \"notifications/initialized\",\n]);\n\n// ============================================================================\n// Method Classification\n// ============================================================================\n\n/**\n * Determines if a given MCP method requires authentication.\n *\n * Discovery methods (tools/list, resources/list, etc.) are open.\n * Execution methods (tools/call) require authentication.\n *\n * @param method The MCP JSON-RPC method (e.g., \"tools/list\", \"tools/call\")\n * @returns true if the method requires authentication\n *\n * @example\n * ```typescript\n * if (isProtectedMcpMethod(body.method)) {\n * await verifyContextRequest({ authorizationHeader: req.headers.authorization });\n * }\n * ```\n */\nexport function isProtectedMcpMethod(method: string): boolean {\n return PROTECTED_MCP_METHODS.has(method);\n}\n\n/**\n * Determines if a given MCP method is explicitly open (no auth).\n *\n * @param method The MCP JSON-RPC method\n * @returns true if the method is known to be open\n */\nexport function isOpenMcpMethod(method: string): boolean {\n return OPEN_MCP_METHODS.has(method);\n}\n\n// ============================================================================\n// Request Verification\n// ============================================================================\n\nexport interface VerifyRequestOptions {\n /** The full Authorization header string (e.g. \"Bearer eyJ...\") */\n authorizationHeader?: string;\n /** Expected Audience (your tool URL) for stricter validation */\n audience?: string;\n}\n\n/**\n * Verifies that an incoming request originated from the Context Protocol Platform.\n *\n * @param options Contains the Authorization header\n * @returns The decoded payload if valid\n * @throws ContextError if invalid\n */\nexport async function verifyContextRequest(options: VerifyRequestOptions) {\n const { authorizationHeader, audience } = options;\n\n if (!authorizationHeader || !authorizationHeader.startsWith(\"Bearer \")) {\n throw new ContextError(\n \"Missing or invalid Authorization header\",\n \"unauthorized\",\n 401\n );\n }\n\n const token = authorizationHeader.split(\" \")[1];\n\n try {\n const publicKey = await importSPKI(CONTEXT_PLATFORM_PUBLIC_KEY_PEM, \"RS256\");\n\n const { payload } = await jwtVerify(token, publicKey, {\n issuer: \"https://ctxprotocol.com\",\n audience: audience,\n });\n\n return payload;\n } catch (error) {\n throw new ContextError(\n \"Invalid Context Protocol signature\",\n \"unauthorized\",\n 401\n );\n }\n}\n\n// ============================================================================\n// Easy-Mode Middleware\n// ============================================================================\n\nexport interface CreateContextMiddlewareOptions {\n /** Expected Audience (your tool URL) for stricter validation */\n audience?: string;\n}\n\n/**\n * Creates an Express/Connect-compatible middleware that secures your MCP endpoint.\n *\n * This is the \"1 line of code\" solution to secure your MCP server.\n * It automatically:\n * - Allows discovery methods (tools/list, initialize) without authentication\n * - Requires and verifies JWT for execution methods (tools/call)\n * - Attaches the verified payload to `req.context` for downstream use\n *\n * @param options Optional configuration\n * @returns Express-compatible middleware function\n *\n * @example\n * ```typescript\n * import express from \"express\";\n * import { createContextMiddleware } from \"@ctxprotocol/sdk\";\n *\n * const app = express();\n * app.use(express.json());\n *\n * // 1 line to secure your endpoint\n * app.use(\"/mcp\", createContextMiddleware());\n *\n * app.post(\"/mcp\", (req, res) => {\n * // req.context contains verified JWT payload (on protected methods)\n * // Handle MCP request...\n * });\n * ```\n */\nexport function createContextMiddleware(options: CreateContextMiddlewareOptions = {}) {\n return async function contextMiddleware(\n req: ContextRequest,\n res: ContextResponse,\n next: NextFunction\n ): Promise<void> {\n const method = req.body?.method as string | undefined;\n\n // Allow discovery methods without authentication\n // Discovery methods (tools/list, initialize, etc.) are open by design\n if (!method || !isProtectedMcpMethod(method)) {\n return next();\n }\n\n // Protected method - require authentication\n try {\n const payload = await verifyContextRequest({\n authorizationHeader: req.headers.authorization,\n audience: options.audience,\n });\n\n // Attach verified payload to request for downstream handlers\n req.context = payload;\n next();\n } catch (error) {\n const statusCode = error instanceof ContextError ? error.statusCode || 401 : 401;\n res.status(statusCode).json({ error: \"Unauthorized\" });\n }\n };\n}\n\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctxprotocol/sdk",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
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",