@ctxprotocol/sdk 0.8.1 → 0.8.3

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.
@@ -9,9 +9,19 @@ interface ContextClientOptions {
9
9
  apiKey: string;
10
10
  /**
11
11
  * Base URL for the Context Protocol API
12
- * @default "https://ctxprotocol.com"
12
+ * @default "https://www.ctxprotocol.com"
13
13
  */
14
14
  baseUrl?: string;
15
+ /**
16
+ * Request timeout for non-streaming API calls in milliseconds.
17
+ * @default 300000
18
+ */
19
+ requestTimeoutMs?: number;
20
+ /**
21
+ * Request timeout for establishing streaming API calls in milliseconds.
22
+ * @default 600000
23
+ */
24
+ streamTimeoutMs?: number;
15
25
  }
16
26
  /**
17
27
  * An individual MCP tool exposed by a tool listing
@@ -30,7 +40,27 @@ interface McpToolRateLimitHints {
30
40
  /** Optional human-readable notes for planning */
31
41
  notes?: string;
32
42
  }
43
+ type DiscoveryMode = "query" | "execute";
44
+ type McpToolSurface = "answer" | "execute" | "both";
45
+ type McpToolLatencyClass = "instant" | "fast" | "slow" | "streaming";
46
+ interface McpToolPricingMeta {
47
+ executeUsd?: string;
48
+ queryUsd?: string;
49
+ [key: string]: unknown;
50
+ }
33
51
  interface McpToolMeta {
52
+ /** Declared method surface */
53
+ surface?: McpToolSurface;
54
+ /** Whether this method can be selected in query mode */
55
+ queryEligible?: boolean;
56
+ /** Declared latency class for planner/runtime gating */
57
+ latencyClass?: McpToolLatencyClass;
58
+ /** Method-level pricing metadata */
59
+ pricing?: McpToolPricingMeta;
60
+ /** Derived discovery flag for execute eligibility */
61
+ executeEligible?: boolean;
62
+ /** Derived discovery field for explicit execute pricing visibility */
63
+ executePriceUsd?: string;
34
64
  /** Context injection requirements handled by the Context runtime */
35
65
  contextRequirements?: string[];
36
66
  /**
@@ -48,6 +78,14 @@ interface McpToolMeta {
48
78
  notes?: string;
49
79
  [key: string]: unknown;
50
80
  }
81
+ interface StructuredMethodGuidanceHints {
82
+ /** Suggested call-order sequence extracted from method descriptions */
83
+ callOrderHints?: string[];
84
+ /** Parameter usage caveats extracted from method descriptions */
85
+ parameterCaveats?: string[];
86
+ /** Edge-case behavior notes extracted from method descriptions */
87
+ edgeCaseNotes?: string[];
88
+ }
51
89
  interface McpTool {
52
90
  /** Name of the MCP tool method */
53
91
  name: string;
@@ -65,6 +103,14 @@ interface McpTool {
65
103
  outputSchema?: Record<string, unknown>;
66
104
  /** MCP metadata extensions (context injection, rate-limit hints) */
67
105
  _meta?: McpToolMeta;
106
+ /** Explicit execute eligibility in discovery responses */
107
+ executeEligible?: boolean;
108
+ /** Explicit execute price visibility in discovery responses */
109
+ executePriceUsd?: string | null;
110
+ /** Whether this method has normalized structured guidance hints */
111
+ hasStructuredGuidance?: boolean;
112
+ /** Optional structured guidance hints derived from the method description */
113
+ structuredGuidance?: StructuredMethodGuidanceHints;
68
114
  }
69
115
  /**
70
116
  * Represents a tool available on the Context Protocol marketplace
@@ -106,6 +152,8 @@ interface Tool {
106
152
  interface SearchResponse {
107
153
  /** Array of matching tools */
108
154
  tools: Tool[];
155
+ /** Discovery mode used by the server */
156
+ mode?: DiscoveryMode;
109
157
  /** The search query that was used */
110
158
  query: string;
111
159
  /** Total number of results */
@@ -119,6 +167,18 @@ interface SearchOptions {
119
167
  query?: string;
120
168
  /** Maximum number of results (1-50, default 10) */
121
169
  limit?: number;
170
+ /** Discovery mode with billing semantics */
171
+ mode?: DiscoveryMode;
172
+ /** Optional explicit method surface filter */
173
+ surface?: McpToolSurface;
174
+ /** Require methods marked query eligible */
175
+ queryEligible?: boolean;
176
+ /** Require explicit method execute pricing */
177
+ requireExecutePricing?: boolean;
178
+ /** Exclude methods by latency class */
179
+ excludeLatencyClasses?: McpToolLatencyClass[];
180
+ /** Convenience switch to exclude slow methods in query mode */
181
+ excludeSlow?: boolean;
122
182
  }
123
183
  /**
124
184
  * Options for executing a tool
@@ -135,12 +195,36 @@ interface ExecuteOptions {
135
195
  * Reuse the same key when retrying the same logical request.
136
196
  */
137
197
  idempotencyKey?: string;
198
+ /** Explicit execute mode label for request clarity */
199
+ mode?: "execute";
200
+ /** Optional execute session identifier */
201
+ sessionId?: string;
202
+ /** Optional per-session spend budget envelope (USD) */
203
+ maxSpendUsd?: string;
204
+ /** Request session closure after this execute call settles */
205
+ closeSession?: boolean;
206
+ }
207
+ type ExecuteSessionStatus = "open" | "closed" | "expired";
208
+ interface ExecuteSessionSpend {
209
+ mode: "execute";
210
+ sessionId: string | null;
211
+ methodPrice: string;
212
+ spent: string;
213
+ remaining: string | null;
214
+ maxSpend: string | null;
215
+ /** Optional lifecycle fields when the API returns session state */
216
+ status?: ExecuteSessionStatus;
217
+ expiresAt?: string;
218
+ closeRequested?: boolean;
219
+ pendingAccruedCount?: number;
220
+ pendingAccruedUsd?: string;
138
221
  }
139
222
  /**
140
223
  * Successful execution response from the API
141
224
  */
142
225
  interface ExecuteApiSuccessResponse {
143
226
  success: true;
227
+ mode: "execute";
144
228
  /** The result data from the tool execution */
145
229
  result: unknown;
146
230
  /** Information about the executed tool */
@@ -148,6 +232,13 @@ interface ExecuteApiSuccessResponse {
148
232
  id: string;
149
233
  name: string;
150
234
  };
235
+ /** Method-level execute pricing used for this call */
236
+ method: {
237
+ name: string;
238
+ executePriceUsd: string;
239
+ };
240
+ /** Spend envelope visibility for execute sessions */
241
+ session: ExecuteSessionSpend;
151
242
  /** Execution duration in milliseconds */
152
243
  durationMs: number;
153
244
  }
@@ -157,19 +248,38 @@ interface ExecuteApiSuccessResponse {
157
248
  interface ExecuteApiErrorResponse {
158
249
  /** Human-readable error message */
159
250
  error: string;
251
+ /** Explicit mode label for clarity */
252
+ mode?: "execute";
160
253
  /** Error code for programmatic handling */
161
254
  code?: ContextErrorCode;
162
255
  /** URL to help resolve the issue */
163
256
  helpUrl?: string;
257
+ /** Optional spend envelope context when available */
258
+ session?: ExecuteSessionSpend;
164
259
  }
165
260
  /**
166
261
  * Raw API response from the execute endpoint
167
262
  */
168
263
  type ExecuteApiResponse = ExecuteApiSuccessResponse | ExecuteApiErrorResponse;
264
+ interface ExecuteSessionStartOptions {
265
+ /** Maximum spend budget for the session (USD string) */
266
+ maxSpendUsd: string;
267
+ }
268
+ interface ExecuteSessionApiSuccessResponse {
269
+ success: true;
270
+ mode: "execute";
271
+ session: ExecuteSessionSpend;
272
+ }
273
+ type ExecuteSessionApiResponse = ExecuteSessionApiSuccessResponse | ExecuteApiErrorResponse;
274
+ interface ExecuteSessionResult {
275
+ mode: "execute";
276
+ session: ExecuteSessionSpend;
277
+ }
169
278
  /**
170
279
  * The resolved result returned to the user after SDK processing
171
280
  */
172
281
  interface ExecutionResult<T = unknown> {
282
+ mode: "execute";
173
283
  /** The data returned by the tool */
174
284
  result: T;
175
285
  /** Information about the executed tool */
@@ -177,9 +287,18 @@ interface ExecutionResult<T = unknown> {
177
287
  id: string;
178
288
  name: string;
179
289
  };
290
+ /** Method-level execute pricing used for this call */
291
+ method: {
292
+ name: string;
293
+ executePriceUsd: string;
294
+ };
295
+ /** Spend envelope visibility for execute calls */
296
+ session: ExecuteSessionSpend;
180
297
  /** Execution duration in milliseconds */
181
298
  durationMs: number;
182
299
  }
300
+ /** Supported orchestration depth modes for query execution. */
301
+ type QueryDepth = "fast" | "auto" | "deep";
183
302
  /**
184
303
  * Options for the agentic query endpoint (pay-per-response).
185
304
  *
@@ -212,6 +331,13 @@ interface QueryOptions {
212
331
  * Useful for large payload workflows where inline JSON is not ideal.
213
332
  */
214
333
  includeDataUrl?: boolean;
334
+ /**
335
+ * Query orchestration depth mode:
336
+ * - `fast`: lower-latency path
337
+ * - `auto`: server decides between fast/deep
338
+ * - `deep`: full completeness-oriented path
339
+ */
340
+ queryDepth?: QueryDepth;
215
341
  /**
216
342
  * Optional idempotency key (UUID recommended).
217
343
  * Reuse the same key when retrying the same logical request.
@@ -300,7 +426,7 @@ type QueryStreamEvent = QueryStreamToolStatusEvent | QueryStreamTextDeltaEvent |
300
426
  /**
301
427
  * Specific error codes returned by the Context Protocol API
302
428
  */
303
- type ContextErrorCode = "unauthorized" | "no_wallet" | "insufficient_allowance" | "payment_failed" | "execution_failed" | "query_failed";
429
+ type ContextErrorCode = "unauthorized" | "no_wallet" | "insufficient_allowance" | "payment_failed" | "execution_failed" | "query_failed" | "invalid_tool_method" | "method_not_execute_eligible" | "invalid_max_spend" | "session_not_found" | "session_forbidden" | "session_closed" | "session_expired" | "max_spend_mismatch" | "session_budget_exceeded";
304
430
  /**
305
431
  * Error thrown by the Context Protocol client
306
432
  */
@@ -318,20 +444,14 @@ declare class Discovery {
318
444
  private client;
319
445
  constructor(client: ContextClient);
320
446
  /**
321
- * Search for tools matching a query string
322
- *
323
- * @param query - The search query (e.g., "gas prices", "nft metadata")
324
- * @param limit - Maximum number of results (1-50, default 10)
325
- * @returns Array of matching tools
447
+ * Search for tools matching a query string.
326
448
  *
327
- * @example
328
- * ```typescript
329
- * const tools = await client.discovery.search("gas prices");
330
- * console.log(tools[0].name); // "Gas Price Oracle"
331
- * console.log(tools[0].mcpTools); // Available methods
332
- * ```
449
+ * Backward-compatible signatures:
450
+ * - `search("gas prices", 10)`
451
+ * - `search({ query: "gas prices", limit: 10, mode: "execute" })`
333
452
  */
334
453
  search(query: string, limit?: number): Promise<Tool[]>;
454
+ search(options: SearchOptions): Promise<Tool[]>;
335
455
  /**
336
456
  * Get featured/popular tools (empty query search)
337
457
  *
@@ -343,7 +463,7 @@ declare class Discovery {
343
463
  * const featured = await client.discovery.getFeatured(5);
344
464
  * ```
345
465
  */
346
- getFeatured(limit?: number): Promise<Tool[]>;
466
+ getFeatured(limit?: number, options?: Omit<SearchOptions, "query" | "limit">): Promise<Tool[]>;
347
467
  }
348
468
 
349
469
  /**
@@ -384,6 +504,19 @@ declare class Tools {
384
504
  * ```
385
505
  */
386
506
  execute<T = unknown>(options: ExecuteOptions): Promise<ExecutionResult<T>>;
507
+ /**
508
+ * Start an execute session with a max spend budget.
509
+ */
510
+ startSession(options: ExecuteSessionStartOptions): Promise<ExecuteSessionResult>;
511
+ /**
512
+ * Fetch current execute session status by ID.
513
+ */
514
+ getSession(sessionId: string): Promise<ExecuteSessionResult>;
515
+ /**
516
+ * Close an execute session by ID.
517
+ */
518
+ closeSession(sessionId: string): Promise<ExecuteSessionResult>;
519
+ private resolveSessionLifecycleResponse;
387
520
  }
388
521
 
389
522
  /**
@@ -493,6 +626,8 @@ declare class Query {
493
626
  declare class ContextClient {
494
627
  private readonly apiKey;
495
628
  private readonly baseUrl;
629
+ private readonly requestTimeoutMs;
630
+ private readonly streamTimeoutMs;
496
631
  private _closed;
497
632
  /**
498
633
  * Discovery resource for searching tools
@@ -515,7 +650,9 @@ declare class ContextClient {
515
650
  *
516
651
  * @param options - Client configuration options
517
652
  * @param options.apiKey - Your Context Protocol API key (format: sk_live_...)
518
- * @param options.baseUrl - Optional base URL override (defaults to https://ctxprotocol.com)
653
+ * @param options.baseUrl - Optional base URL override (defaults to https://www.ctxprotocol.com)
654
+ * @param options.requestTimeoutMs - Optional timeout for non-streaming requests (default 300000ms)
655
+ * @param options.streamTimeoutMs - Optional timeout for establishing stream requests (default 600000ms)
519
656
  */
520
657
  constructor(options: ContextClientOptions);
521
658
  /**
@@ -525,7 +662,7 @@ declare class ContextClient {
525
662
  close(): void;
526
663
  /**
527
664
  * Internal method for making authenticated HTTP requests
528
- * Includes timeout (30s) and retry with exponential backoff for transient errors
665
+ * Includes timeout and retry with exponential backoff for transient errors
529
666
  *
530
667
  * @internal
531
668
  */
@@ -533,10 +670,11 @@ declare class ContextClient {
533
670
  /**
534
671
  * Internal method for making authenticated HTTP requests that returns
535
672
  * the raw Response object. Used for streaming endpoints (SSE).
673
+ * Includes a configurable timeout for stream setup.
536
674
  *
537
675
  * @internal
538
676
  */
539
677
  _fetchRaw(endpoint: string, options?: RequestInit): Promise<Response>;
540
678
  }
541
679
 
542
- export { ContextClient, type ContextClientOptions, ContextError, type ContextErrorCode, Discovery, type ExecuteApiErrorResponse, type ExecuteApiResponse, type ExecuteApiSuccessResponse, type ExecuteOptions, type ExecutionResult, type McpTool, type McpToolMeta, type McpToolRateLimitHints, Query, type QueryApiResponse, type QueryApiSuccessResponse, type QueryCost, type QueryOptions, type QueryResult, type QueryStreamDoneEvent, type QueryStreamEvent, type QueryStreamTextDeltaEvent, type QueryStreamToolStatusEvent, type QueryToolUsage, type SearchOptions, type SearchResponse, type Tool, Tools };
680
+ export { ContextClient, type ContextClientOptions, ContextError, type ContextErrorCode, Discovery, type ExecuteApiErrorResponse, type ExecuteApiResponse, type ExecuteApiSuccessResponse, type ExecuteOptions, type ExecuteSessionApiResponse, type ExecuteSessionApiSuccessResponse, type ExecuteSessionResult, type ExecuteSessionSpend, type ExecuteSessionStartOptions, type ExecuteSessionStatus, type ExecutionResult, type McpTool, type McpToolMeta, type McpToolRateLimitHints, Query, type QueryApiResponse, type QueryApiSuccessResponse, type QueryCost, type QueryOptions, type QueryResult, type QueryStreamDoneEvent, type QueryStreamEvent, type QueryStreamTextDeltaEvent, type QueryStreamToolStatusEvent, type QueryToolUsage, type SearchOptions, type SearchResponse, type Tool, Tools };
@@ -15,27 +15,36 @@ var Discovery = class {
15
15
  constructor(client) {
16
16
  this.client = client;
17
17
  }
18
- /**
19
- * Search for tools matching a query string
20
- *
21
- * @param query - The search query (e.g., "gas prices", "nft metadata")
22
- * @param limit - Maximum number of results (1-50, default 10)
23
- * @returns Array of matching tools
24
- *
25
- * @example
26
- * ```typescript
27
- * const tools = await client.discovery.search("gas prices");
28
- * console.log(tools[0].name); // "Gas Price Oracle"
29
- * console.log(tools[0].mcpTools); // Available methods
30
- * ```
31
- */
32
- async search(query, limit) {
18
+ async search(queryOrOptions, limit) {
19
+ const options = typeof queryOrOptions === "string" ? { query: queryOrOptions, limit } : queryOrOptions;
33
20
  const params = new URLSearchParams();
21
+ const query = options.query ?? "";
34
22
  if (query) {
35
23
  params.set("q", query);
36
24
  }
37
- if (limit !== void 0) {
38
- params.set("limit", String(limit));
25
+ if (options.limit !== void 0) {
26
+ params.set("limit", String(options.limit));
27
+ }
28
+ if (options.mode) {
29
+ params.set("mode", options.mode);
30
+ }
31
+ if (options.surface) {
32
+ params.set("surface", options.surface);
33
+ }
34
+ if (options.queryEligible !== void 0) {
35
+ params.set("queryEligible", String(options.queryEligible));
36
+ }
37
+ if (options.requireExecutePricing !== void 0) {
38
+ params.set(
39
+ "requireExecutePricing",
40
+ String(options.requireExecutePricing)
41
+ );
42
+ }
43
+ if (options.excludeLatencyClasses && options.excludeLatencyClasses.length > 0) {
44
+ params.set("excludeLatency", options.excludeLatencyClasses.join(","));
45
+ }
46
+ if (options.excludeSlow !== void 0) {
47
+ params.set("excludeSlow", String(options.excludeSlow));
39
48
  }
40
49
  const queryString = params.toString();
41
50
  const endpoint = `/api/v1/tools/search${queryString ? `?${queryString}` : ""}`;
@@ -53,8 +62,12 @@ var Discovery = class {
53
62
  * const featured = await client.discovery.getFeatured(5);
54
63
  * ```
55
64
  */
56
- async getFeatured(limit) {
57
- return this.search("", limit);
65
+ async getFeatured(limit, options) {
66
+ return this.search({
67
+ ...options ?? {},
68
+ query: "",
69
+ ...limit !== void 0 ? { limit } : {}
70
+ });
58
71
  }
59
72
  };
60
73
 
@@ -95,14 +108,31 @@ var Tools = class {
95
108
  * ```
96
109
  */
97
110
  async execute(options) {
98
- const { toolId, toolName, args, idempotencyKey } = options;
111
+ const {
112
+ toolId,
113
+ toolName,
114
+ args,
115
+ idempotencyKey,
116
+ mode,
117
+ sessionId,
118
+ maxSpendUsd,
119
+ closeSession
120
+ } = options;
99
121
  const headers = idempotencyKey ? { "Idempotency-Key": idempotencyKey } : void 0;
100
122
  const response = await this.client._fetch(
101
123
  "/api/v1/tools/execute",
102
124
  {
103
125
  method: "POST",
104
126
  headers,
105
- body: JSON.stringify({ toolId, toolName, args })
127
+ body: JSON.stringify({
128
+ toolId,
129
+ toolName,
130
+ args,
131
+ mode: mode ?? "execute",
132
+ sessionId,
133
+ maxSpendUsd,
134
+ closeSession
135
+ })
106
136
  }
107
137
  );
108
138
  if ("error" in response) {
@@ -116,13 +146,79 @@ var Tools = class {
116
146
  }
117
147
  if (response.success) {
118
148
  return {
149
+ mode: response.mode,
119
150
  result: response.result,
120
151
  tool: response.tool,
152
+ method: response.method,
153
+ session: response.session,
121
154
  durationMs: response.durationMs
122
155
  };
123
156
  }
124
157
  throw new ContextError("Unexpected response format from API");
125
158
  }
159
+ /**
160
+ * Start an execute session with a max spend budget.
161
+ */
162
+ async startSession(options) {
163
+ const response = await this.client._fetch(
164
+ "/api/v1/tools/execute/sessions",
165
+ {
166
+ method: "POST",
167
+ body: JSON.stringify({
168
+ mode: "execute",
169
+ maxSpendUsd: options.maxSpendUsd
170
+ })
171
+ }
172
+ );
173
+ return this.resolveSessionLifecycleResponse(response);
174
+ }
175
+ /**
176
+ * Fetch current execute session status by ID.
177
+ */
178
+ async getSession(sessionId) {
179
+ if (!sessionId) {
180
+ throw new ContextError("sessionId is required");
181
+ }
182
+ const encodedSessionId = encodeURIComponent(sessionId);
183
+ const response = await this.client._fetch(
184
+ `/api/v1/tools/execute/sessions/${encodedSessionId}`
185
+ );
186
+ return this.resolveSessionLifecycleResponse(response);
187
+ }
188
+ /**
189
+ * Close an execute session by ID.
190
+ */
191
+ async closeSession(sessionId) {
192
+ if (!sessionId) {
193
+ throw new ContextError("sessionId is required");
194
+ }
195
+ const encodedSessionId = encodeURIComponent(sessionId);
196
+ const response = await this.client._fetch(
197
+ `/api/v1/tools/execute/sessions/${encodedSessionId}/close`,
198
+ {
199
+ method: "POST",
200
+ body: JSON.stringify({ mode: "execute" })
201
+ }
202
+ );
203
+ return this.resolveSessionLifecycleResponse(response);
204
+ }
205
+ resolveSessionLifecycleResponse(response) {
206
+ if ("error" in response) {
207
+ throw new ContextError(
208
+ response.error,
209
+ response.code,
210
+ void 0,
211
+ response.helpUrl
212
+ );
213
+ }
214
+ if (response.success) {
215
+ return {
216
+ mode: response.mode,
217
+ session: response.session
218
+ };
219
+ }
220
+ throw new ContextError("Unexpected response format from API");
221
+ }
126
222
  };
127
223
 
128
224
  // src/client/resources/query.ts
@@ -175,6 +271,7 @@ var Query = class {
175
271
  modelId: opts.modelId,
176
272
  includeData: opts.includeData,
177
273
  includeDataUrl: opts.includeDataUrl,
274
+ queryDepth: opts.queryDepth,
178
275
  stream: false
179
276
  })
180
277
  }
@@ -240,6 +337,7 @@ var Query = class {
240
337
  modelId: opts.modelId,
241
338
  includeData: opts.includeData,
242
339
  includeDataUrl: opts.includeDataUrl,
340
+ queryDepth: opts.queryDepth,
243
341
  stream: true
244
342
  })
245
343
  });
@@ -285,9 +383,14 @@ var Query = class {
285
383
  };
286
384
 
287
385
  // src/client/client.ts
386
+ var DEFAULT_BASE_URL = "https://www.ctxprotocol.com";
387
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e5;
388
+ var DEFAULT_STREAM_TIMEOUT_MS = 6e5;
288
389
  var ContextClient = class {
289
390
  apiKey;
290
391
  baseUrl;
392
+ requestTimeoutMs;
393
+ streamTimeoutMs;
291
394
  _closed = false;
292
395
  /**
293
396
  * Discovery resource for searching tools
@@ -310,14 +413,26 @@ var ContextClient = class {
310
413
  *
311
414
  * @param options - Client configuration options
312
415
  * @param options.apiKey - Your Context Protocol API key (format: sk_live_...)
313
- * @param options.baseUrl - Optional base URL override (defaults to https://ctxprotocol.com)
416
+ * @param options.baseUrl - Optional base URL override (defaults to https://www.ctxprotocol.com)
417
+ * @param options.requestTimeoutMs - Optional timeout for non-streaming requests (default 300000ms)
418
+ * @param options.streamTimeoutMs - Optional timeout for establishing stream requests (default 600000ms)
314
419
  */
315
420
  constructor(options) {
316
421
  if (!options.apiKey) {
317
422
  throw new ContextError("API key is required");
318
423
  }
424
+ const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
425
+ const streamTimeoutMs = options.streamTimeoutMs ?? DEFAULT_STREAM_TIMEOUT_MS;
426
+ if (!Number.isFinite(requestTimeoutMs) || requestTimeoutMs <= 0) {
427
+ throw new ContextError("requestTimeoutMs must be a positive number");
428
+ }
429
+ if (!Number.isFinite(streamTimeoutMs) || streamTimeoutMs <= 0) {
430
+ throw new ContextError("streamTimeoutMs must be a positive number");
431
+ }
319
432
  this.apiKey = options.apiKey;
320
- this.baseUrl = (options.baseUrl ?? "https://ctxprotocol.com").replace(/\/$/, "");
433
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
434
+ this.requestTimeoutMs = requestTimeoutMs;
435
+ this.streamTimeoutMs = streamTimeoutMs;
321
436
  this.discovery = new Discovery(this);
322
437
  this.tools = new Tools(this);
323
438
  this.query = new Query(this);
@@ -331,7 +446,7 @@ var ContextClient = class {
331
446
  }
332
447
  /**
333
448
  * Internal method for making authenticated HTTP requests
334
- * Includes timeout (30s) and retry with exponential backoff for transient errors
449
+ * Includes timeout and retry with exponential backoff for transient errors
335
450
  *
336
451
  * @internal
337
452
  */
@@ -341,7 +456,7 @@ var ContextClient = class {
341
456
  }
342
457
  const url = `${this.baseUrl}${endpoint}`;
343
458
  const maxRetries = 3;
344
- const timeoutMs = 3e4;
459
+ const timeoutMs = this.requestTimeoutMs;
345
460
  let lastError;
346
461
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
347
462
  const controller = new AbortController();
@@ -409,6 +524,7 @@ var ContextClient = class {
409
524
  /**
410
525
  * Internal method for making authenticated HTTP requests that returns
411
526
  * the raw Response object. Used for streaming endpoints (SSE).
527
+ * Includes a configurable timeout for stream setup.
412
528
  *
413
529
  * @internal
414
530
  */
@@ -417,14 +533,32 @@ var ContextClient = class {
417
533
  throw new ContextError("Client has been closed");
418
534
  }
419
535
  const url = `${this.baseUrl}${endpoint}`;
420
- const response = await fetch(url, {
421
- ...options,
422
- headers: {
423
- "Content-Type": "application/json",
424
- Authorization: `Bearer ${this.apiKey}`,
425
- ...options.headers
536
+ const controller = new AbortController();
537
+ const timeout = setTimeout(() => controller.abort(), this.streamTimeoutMs);
538
+ let response;
539
+ try {
540
+ response = await fetch(url, {
541
+ ...options,
542
+ signal: controller.signal,
543
+ headers: {
544
+ "Content-Type": "application/json",
545
+ Authorization: `Bearer ${this.apiKey}`,
546
+ ...options.headers
547
+ }
548
+ });
549
+ } catch (error) {
550
+ clearTimeout(timeout);
551
+ const lastError = error instanceof Error ? error : new Error(String(error));
552
+ if (lastError.name === "AbortError") {
553
+ throw new ContextError(
554
+ `Streaming request timed out after ${this.streamTimeoutMs / 1e3}s`,
555
+ void 0,
556
+ 408
557
+ );
426
558
  }
427
- });
559
+ throw new ContextError(lastError.message);
560
+ }
561
+ clearTimeout(timeout);
428
562
  if (!response.ok) {
429
563
  let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
430
564
  let errorCode;