@runpod/mcp-server 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/http.cjs CHANGED
@@ -99,15 +99,27 @@ function mapPodUpdateToV2(params) {
99
99
  ...compact({ name: params.name })
100
100
  };
101
101
  }
102
- function nestedOrUndefined(obj) {
103
- const c = compact(obj);
104
- return Object.keys(c).length ? c : void 0;
102
+ var DEFAULT_ENDPOINT_TYPE = "QUEUE";
103
+ var DEFAULT_SCALER_VALUE = 4;
104
+ function defaultScalerType(endpointType) {
105
+ return endpointType === "LOAD_BALANCER" ? "REQUEST_COUNT" : "QUEUE_DELAY";
106
+ }
107
+ function endpointScaling(scalerType, value) {
108
+ return scalerType === "REQUEST_COUNT" ? { type: "REQUEST_COUNT", requestCount: value } : { type: "QUEUE_DELAY", queueDelay: value };
105
109
  }
106
110
  function endpointGpuConfig(pools, count) {
107
111
  if (!pools?.length) return void 0;
108
112
  return compact({ pools, count });
109
113
  }
110
- function mapEndpointToV2(params) {
114
+ function endpointWorkers(params) {
115
+ const workers = compact({
116
+ min: params.workersMin,
117
+ max: params.workersMax,
118
+ idleTimeout: params.idleTimeout
119
+ });
120
+ return Object.keys(workers).length ? workers : void 0;
121
+ }
122
+ function endpointCommonToV2(params) {
111
123
  return compact({
112
124
  name: params.name,
113
125
  image: params.imageName,
@@ -117,23 +129,33 @@ function mapEndpointToV2(params) {
117
129
  env: params.env,
118
130
  registry: params.containerRegistryAuthId,
119
131
  gpu: endpointGpuConfig(params.gpuPoolIds, params.gpuCount),
120
- workers: nestedOrUndefined({
121
- min: params.workersMin,
122
- max: params.workersMax
123
- }),
124
- scaling: nestedOrUndefined({
125
- type: params.scalerType,
126
- value: params.scalerValue,
127
- idleTimeout: params.idleTimeout
128
- }),
132
+ workers: endpointWorkers(params),
129
133
  dataCenterIds: params.dataCenterIds?.length ? params.dataCenterIds : void 0,
130
134
  networkVolumes: params.networkVolumeIds?.length ? params.networkVolumeIds : void 0,
131
135
  timeout: params.executionTimeoutMs,
132
136
  flashboot: params.flashboot
133
137
  });
134
138
  }
135
- var mapEndpointCreateToV2 = mapEndpointToV2;
136
- var mapEndpointUpdateToV2 = mapEndpointToV2;
139
+ function mapEndpointCreateToV2(params) {
140
+ const endpointType = params.endpointType ?? DEFAULT_ENDPOINT_TYPE;
141
+ return compact({
142
+ ...endpointCommonToV2(params),
143
+ type: endpointType,
144
+ scaling: endpointScaling(
145
+ params.scalerType ?? defaultScalerType(endpointType),
146
+ params.scalerValue ?? DEFAULT_SCALER_VALUE
147
+ )
148
+ });
149
+ }
150
+ function mapEndpointUpdateToV2(params) {
151
+ return compact({
152
+ ...endpointCommonToV2(params),
153
+ scaling: params.scalerType ? endpointScaling(
154
+ params.scalerType,
155
+ params.scalerValue ?? DEFAULT_SCALER_VALUE
156
+ ) : void 0
157
+ });
158
+ }
137
159
  function mapNetworkVolumeCreateToV2(params) {
138
160
  return compact({
139
161
  name: params.name,
@@ -389,7 +411,7 @@ function resolveBackend(opts) {
389
411
 
390
412
  // src/server.ts
391
413
  var SERVER_NAME = "Runpod API Server";
392
- var SERVER_VERSION = true ? "3.0.0" : "dev";
414
+ var SERVER_VERSION = true ? "3.1.0" : "dev";
393
415
  function createServer(version = SERVER_VERSION) {
394
416
  return new import_mcp.McpServer({
395
417
  name: SERVER_NAME,
@@ -566,7 +588,7 @@ async function streamLogsReply(rt, tool, params) {
566
588
  }
567
589
 
568
590
  // src/tools/runtime.ts
569
- var MCP_SERVER_VERSION = true ? "3.0.0" : "dev";
591
+ var MCP_SERVER_VERSION = true ? "3.1.0" : "dev";
570
592
  var SESSION_ID = (0, import_node_crypto.randomUUID)();
571
593
  function trackingHeaders(server, ctx) {
572
594
  const info = server.server.getClientVersion();
@@ -1952,11 +1974,22 @@ function registerPodTools(server, rt) {
1952
1974
 
1953
1975
  // src/tools/endpoints.ts
1954
1976
  var import_zod8 = require("zod");
1977
+ var MIN_QUEUE_DELAY = 0.5;
1978
+ var MIN_REQUEST_COUNT = 1;
1979
+ var MIN_IDLE_TIMEOUT = 1;
1980
+ var MAX_IDLE_TIMEOUT = 3600;
1981
+ function scalerValueError(scalerType, scalerValue) {
1982
+ if (scalerType !== "REQUEST_COUNT" || scalerValue === void 0) return;
1983
+ if (!Number.isInteger(scalerValue) || scalerValue < MIN_REQUEST_COUNT) {
1984
+ return `REQUEST_COUNT scales on whole in-flight requests per worker, so scalerValue must be an integer >= ${MIN_REQUEST_COUNT} (got ${scalerValue}). For a fractional target use scalerType QUEUE_DELAY, which takes seconds >= ${MIN_QUEUE_DELAY}.`;
1985
+ }
1986
+ return;
1987
+ }
1955
1988
  function registerEndpointTools(server, rt) {
1956
1989
  const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
1957
1990
  server.tool(
1958
1991
  "list-endpoints",
1959
- "List your Serverless endpoints, optionally expanding template and worker details (v1 only). Paginated via limit/cursor.",
1992
+ "List your Serverless endpoints, optionally expanding template and worker details (v1 only). Paginated via limit/cursor. On v2 each endpoint carries its request routing in `type` and the URLs to call it in `requestUrls`.",
1960
1993
  {
1961
1994
  ...listPaginationParams,
1962
1995
  includeTemplate: import_zod8.z.boolean().optional().describe("Include template information (v1 only)"),
@@ -1991,7 +2024,7 @@ function registerEndpointTools(server, rt) {
1991
2024
  );
1992
2025
  server.tool(
1993
2026
  "get-endpoint",
1994
- "Get one Serverless endpoint by id, optionally expanding template and worker details (v1 only).",
2027
+ "Get one Serverless endpoint by id, optionally expanding template and worker details (v1 only). On v2 the reply carries `type` (queue-based vs load-balancing routing) and `requestUrls` \u2014 the run/runsync/status/... URLs for a queue endpoint, or the base + health URLs for a load-balancing one. Read those rather than constructing endpoint URLs by hand.",
1995
2028
  {
1996
2029
  endpointId: import_zod8.z.string().describe("ID of the endpoint to retrieve"),
1997
2030
  includeTemplate: import_zod8.z.boolean().optional().describe("Include template information (v1 only)"),
@@ -2023,11 +2056,14 @@ function registerEndpointTools(server, rt) {
2023
2056
  );
2024
2057
  server.tool(
2025
2058
  "create-endpoint",
2026
- "Create a Serverless endpoint. On v2 (default), pass an inline config: imageName + gpuPoolIds (GPU pool names from list-gpu-types \u2014 the `pool` field, e.g. AMPERE_80/ADA_24) plus optional workers/scaling/disk/env. On v1, pass a templateId instead. Worker min/max set autoscaling bounds (min 0 = scale to zero).",
2059
+ "Create a Serverless endpoint. On v2 (default), pass an inline config: imageName + gpuPoolIds (GPU pool names from list-gpu-types \u2014 the `pool` field, e.g. AMPERE_80/ADA_24) plus optional workers/scaling/disk/env. On v1, pass a templateId instead. Worker min/max set autoscaling bounds (min 0 = scale to zero). Use endpointType to pick queue-based (default) or load-balancing request routing; the response `requestUrls` carries the URLs to call the endpoint with.",
2027
2060
  {
2028
2061
  name: import_zod8.z.string().optional().describe("Name for the endpoint"),
2029
2062
  // --- v2 inline-config fields ---
2030
2063
  imageName: import_zod8.z.string().optional().describe("Docker image (v2). Required on v2 instead of a templateId."),
2064
+ endpointType: import_zod8.z.enum(["QUEUE", "LOAD_BALANCER"]).optional().describe(
2065
+ "How requests reach the workers (v2). QUEUE (default) = submit jobs through the managed queue (run/runsync/status/...). LOAD_BALANCER = send HTTP requests straight to worker-defined paths, and forces scalerType REQUEST_COUNT. Fixed at creation \u2014 update-endpoint cannot change it."
2066
+ ),
2031
2067
  gpuPoolIds: import_zod8.z.array(import_zod8.z.string()).optional().describe(
2032
2068
  'GPU pool names (v2, required). The `pool` field from list-gpu-types, e.g. ["AMPERE_80"]. NOT GPU type ids.'
2033
2069
  ),
@@ -2047,9 +2083,15 @@ function registerEndpointTools(server, rt) {
2047
2083
  // --- shared autoscaling fields ---
2048
2084
  workersMin: import_zod8.z.number().optional().describe("Minimum number of workers"),
2049
2085
  workersMax: import_zod8.z.number().optional().describe("Maximum number of workers"),
2050
- scalerType: import_zod8.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe("Autoscaler type"),
2051
- scalerValue: import_zod8.z.number().optional().describe("Autoscaler target value"),
2052
- idleTimeout: import_zod8.z.number().optional().describe("Idle timeout in seconds before scaling a worker down"),
2086
+ scalerType: import_zod8.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe(
2087
+ "Autoscaler signal. QUEUE_DELAY scales on how long requests wait in the queue (queue endpoints only); REQUEST_COUNT scales on in-flight requests per worker. Defaults to QUEUE_DELAY for QUEUE endpoints and REQUEST_COUNT for LOAD_BALANCER ones."
2088
+ ),
2089
+ scalerValue: import_zod8.z.number().min(MIN_QUEUE_DELAY).optional().describe(
2090
+ "Autoscaler target for the chosen scalerType \u2014 seconds of queue delay (min 0.5) or in-flight requests per worker (integer, min 1). Defaults to 4."
2091
+ ),
2092
+ idleTimeout: import_zod8.z.number().int().min(MIN_IDLE_TIMEOUT).max(MAX_IDLE_TIMEOUT).optional().describe(
2093
+ "Idle timeout in seconds before scaling a worker down (1-3600). Does not apply to QUEUE endpoints scaling on REQUEST_COUNT."
2094
+ ),
2053
2095
  dataCenterIds: import_zod8.z.array(import_zod8.z.string()).optional().describe("List of preferred data centers")
2054
2096
  },
2055
2097
  { title: "Create endpoint", ...WRITE },
@@ -2074,6 +2116,19 @@ function registerEndpointTools(server, rt) {
2074
2116
  status: 400
2075
2117
  });
2076
2118
  }
2119
+ if (params.endpointType === "LOAD_BALANCER" && params.scalerType === "QUEUE_DELAY") {
2120
+ return jsonReply2({
2121
+ error: "LOAD_BALANCER endpoints have no request queue, so they cannot scale on QUEUE_DELAY. Use scalerType REQUEST_COUNT (the default for this endpoint type), or create a QUEUE endpoint instead.",
2122
+ status: 400
2123
+ });
2124
+ }
2125
+ const scalerError = scalerValueError(
2126
+ params.scalerType ?? (params.endpointType === "LOAD_BALANCER" ? "REQUEST_COUNT" : void 0),
2127
+ params.scalerValue
2128
+ );
2129
+ if (scalerError) {
2130
+ return jsonReply2({ error: scalerError, status: 400 });
2131
+ }
2077
2132
  const body = backend.mapCreate(params);
2078
2133
  const result2 = await callRestUrl(
2079
2134
  `${backend.base}${backend.list}`,
@@ -2111,15 +2166,21 @@ function registerEndpointTools(server, rt) {
2111
2166
  );
2112
2167
  server.tool(
2113
2168
  "update-endpoint",
2114
- "Update a Serverless endpoint's config. On v2 you can change image/disk/env/ports/registry/workers/scaling/networkVolumes/timeout/flashboot; on v1, scaling fields (worker min/max, idle timeout, scaler type/value, name). Only provided fields change.",
2169
+ "Update a Serverless endpoint's config. On v2 you can change image/disk/env/ports/registry/workers/scaling/networkVolumes/timeout/flashboot; on v1, scaling fields (worker min/max, idle timeout, scaler type/value, name). Only provided fields change. An endpoint's request routing (queue vs load balancer) is fixed at creation and cannot be changed here \u2014 recreate the endpoint instead.",
2115
2170
  {
2116
2171
  endpointId: import_zod8.z.string().describe("ID of the endpoint to update"),
2117
2172
  name: import_zod8.z.string().optional().describe("New name for the endpoint"),
2118
2173
  workersMin: import_zod8.z.number().optional().describe("New minimum number of workers"),
2119
2174
  workersMax: import_zod8.z.number().optional().describe("New maximum number of workers"),
2120
- idleTimeout: import_zod8.z.number().optional().describe("New idle timeout in seconds"),
2121
- scalerType: import_zod8.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe("Scaler type"),
2122
- scalerValue: import_zod8.z.number().optional().describe("Scaler value"),
2175
+ idleTimeout: import_zod8.z.number().int().min(MIN_IDLE_TIMEOUT).max(MAX_IDLE_TIMEOUT).optional().describe(
2176
+ "New idle timeout in seconds (1-3600). Does not apply to queue endpoints scaling on REQUEST_COUNT."
2177
+ ),
2178
+ scalerType: import_zod8.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe(
2179
+ "New autoscaler signal. Switchable on queue endpoints; load-balancing endpoints only accept REQUEST_COUNT."
2180
+ ),
2181
+ scalerValue: import_zod8.z.number().min(MIN_QUEUE_DELAY).optional().describe(
2182
+ "New autoscaler target \u2014 seconds of queue delay (min 0.5) or in-flight requests per worker (integer, min 1). Applies to the endpoint's current scalerType unless you also pass a new one."
2183
+ ),
2123
2184
  // --- v2-only inline-config fields ---
2124
2185
  imageName: import_zod8.z.string().optional().describe("New Docker image (v2)"),
2125
2186
  gpuPoolIds: import_zod8.z.array(import_zod8.z.string()).optional().describe('New GPU pool names (v2), e.g. ["AMPERE_80"]'),
@@ -2137,13 +2198,28 @@ function registerEndpointTools(server, rt) {
2137
2198
  async (params) => {
2138
2199
  const { endpointId, ...updateParams } = params;
2139
2200
  const backend = backendFor("endpoints");
2140
- const body = backend.mapUpdate(updateParams);
2141
- const result = await callRestUrl(
2142
- `${backend.base}${backend.get(endpointId)}`,
2143
- "PATCH",
2144
- body
2201
+ const url = `${backend.base}${backend.get(endpointId)}`;
2202
+ if (backend.version !== "v2") {
2203
+ const body2 = backend.mapUpdate(updateParams);
2204
+ return jsonReply2(await callRestUrl(url, "PATCH", body2));
2205
+ }
2206
+ let scalerType = updateParams.scalerType;
2207
+ if (scalerType === void 0 && updateParams.scalerValue !== void 0) {
2208
+ const current = await callRestUrl(url);
2209
+ scalerType = current?.scaling?.type === "REQUEST_COUNT" ? "REQUEST_COUNT" : "QUEUE_DELAY";
2210
+ }
2211
+ const scalerError = scalerValueError(
2212
+ scalerType,
2213
+ updateParams.scalerValue
2145
2214
  );
2146
- return jsonReply2(result);
2215
+ if (scalerError) {
2216
+ return jsonReply2({ error: scalerError, status: 400 });
2217
+ }
2218
+ const body = backend.mapUpdate({
2219
+ ...updateParams,
2220
+ scalerType
2221
+ });
2222
+ return jsonReply2(await callRestUrl(url, "PATCH", body));
2147
2223
  }
2148
2224
  );
2149
2225
  server.tool(
@@ -2191,7 +2267,7 @@ function registerEndpointTools(server, rt) {
2191
2267
  );
2192
2268
  server.tool(
2193
2269
  "list-endpoint-workers",
2194
- "List the workers backing a Serverless endpoint, with their status and an aggregate summary. v2-only \u2014 returns a 501 notice on the v1 API. Paginated via limit/cursor.",
2270
+ "List the workers backing a Serverless endpoint, with their status and an aggregate summary. v2-only \u2014 returns a 501 notice on the v1 API. An UNHEALTHY worker usually means the container is crash-looping (repeated starts that exit before the handler runs) \u2014 jobs on the endpoint will sit IN_QUEUE even though this is a worker failure, not a capacity shortage; inspect it with stream-worker-logs. Zero workers while jobs queue means the endpoint is waiting for GPU capacity. Paginated via limit/cursor.",
2195
2271
  {
2196
2272
  ...listPaginationParams,
2197
2273
  endpointId: import_zod8.z.string().describe("ID of the Serverless endpoint whose workers to list")
@@ -2217,7 +2293,7 @@ function registerEndpointTools(server, rt) {
2217
2293
  );
2218
2294
  server.tool(
2219
2295
  "stream-worker-logs",
2220
- "Fetch a bounded snapshot of a serverless worker's live logs (container and/or system) via Server-Sent Events. v2-only \u2014 returns a 501 notice on the v1 API. Get the workerId from list-endpoint-workers. Reads for up to maxWaitMs (default 5s) and returns the collected log lines; use `tail` to backfill recent lines first. Large output is truncated (see the `truncated` flag).",
2296
+ "Fetch a bounded snapshot of a serverless worker's live logs (container and/or system) via Server-Sent Events. v2-only \u2014 returns a 501 notice on the v1 API. Get the workerId from list-endpoint-workers. Use this to diagnose UNHEALTHY workers: repeated `start container` system lines with no container output is the crash-loop signature (the container exits before the handler runs). Reads for up to maxWaitMs (default 5s) and returns the collected log lines; use `tail` to backfill recent lines first. Large output is truncated (see the `truncated` flag).",
2221
2297
  {
2222
2298
  endpointId: import_zod8.z.string().describe("ID of the Serverless endpoint the worker belongs to"),
2223
2299
  workerId: import_zod8.z.string().describe(
@@ -2240,8 +2316,67 @@ function registerEndpointTools(server, rt) {
2240
2316
 
2241
2317
  // src/tools/jobs.ts
2242
2318
  var import_zod9 = require("zod");
2319
+ var DIAGNOSIS_TTL_MS = 15e3;
2320
+ var MAX_DIAGNOSIS_ENTRIES = 500;
2321
+ var diagnosisCache = /* @__PURE__ */ new Map();
2243
2322
  function registerJobTools(server, rt) {
2244
- const { jsonReply: jsonReply2, serverlessRequest } = rt;
2323
+ const { jsonReply: jsonReply2, serverlessRequest, backendFor, callRestUrl } = rt;
2324
+ async function diagnoseQueuedJob(endpointId) {
2325
+ const cached = diagnosisCache.get(endpointId);
2326
+ if (cached && cached.expiresAt > Date.now()) {
2327
+ return cached.value;
2328
+ }
2329
+ diagnosisCache.delete(endpointId);
2330
+ try {
2331
+ const backend = backendFor("workers");
2332
+ if (backend.version !== "v2") return null;
2333
+ const raw = await callRestUrl(
2334
+ `${backend.base}/serverless/${endpointId}/workers`
2335
+ );
2336
+ const summary = raw?.summary;
2337
+ const workers = raw?.workers ?? [];
2338
+ if (!summary) return null;
2339
+ const remember = (value) => {
2340
+ if (diagnosisCache.size >= MAX_DIAGNOSIS_ENTRIES) {
2341
+ const oldest = diagnosisCache.keys().next().value;
2342
+ if (oldest !== void 0) diagnosisCache.delete(oldest);
2343
+ }
2344
+ diagnosisCache.set(endpointId, {
2345
+ value,
2346
+ expiresAt: Date.now() + DIAGNOSIS_TTL_MS
2347
+ });
2348
+ return value;
2349
+ };
2350
+ if ((summary.unhealthy ?? 0) > 0) {
2351
+ const badIds = workers.filter((w) => w.status === "UNHEALTHY").map((w) => w.id).join(", ");
2352
+ return remember({
2353
+ workerHealth: summary,
2354
+ hint: `${summary.unhealthy} of ${summary.total} worker(s) on this endpoint are UNHEALTHY \u2014 the container is likely crash-looping (repeated starts that exit before the handler runs). The job can stay IN_QUEUE indefinitely; this is a worker failure, NOT a capacity shortage. Inspect the logs with stream-worker-logs${badIds ? ` (workerId: ${badIds})` : ""}.`
2355
+ });
2356
+ }
2357
+ if ((summary.total ?? 0) === 0) {
2358
+ return remember({
2359
+ workerHealth: summary,
2360
+ hint: "No workers are scheduled for this endpoint yet \u2014 it may simply be scaling up from zero (normal for the first seconds of a cold start), or it is waiting for GPU capacity / its GPU-CUDA constraints exclude the currently-available hosts. If this persists, check availability with list-gpu-types or widen the gpuIds/CUDA settings."
2361
+ });
2362
+ }
2363
+ if ((summary.throttled ?? 0) > 0 && (summary.running ?? 0) === 0) {
2364
+ return remember({
2365
+ workerHealth: summary,
2366
+ hint: "Workers exist but are throttled \u2014 the hosts are at capacity right now; the job should start when capacity frees up."
2367
+ });
2368
+ }
2369
+ if ((summary.initializing ?? 0) > 0) {
2370
+ return remember({
2371
+ workerHealth: summary,
2372
+ hint: "A worker is initializing \u2014 likely a cold start (image pull / model load); the job should start soon."
2373
+ });
2374
+ }
2375
+ return remember({ workerHealth: summary, hint: "" });
2376
+ } catch {
2377
+ return null;
2378
+ }
2379
+ }
2245
2380
  const endpointIdSchema = import_zod9.z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid endpoint ID format");
2246
2381
  const jobIdSchema = import_zod9.z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid job ID format");
2247
2382
  const inputSchema = import_zod9.z.record(import_zod9.z.unknown()).describe(
@@ -2315,7 +2450,7 @@ function registerJobTools(server, rt) {
2315
2450
  );
2316
2451
  server.tool(
2317
2452
  "get-job-status",
2318
- "Check the status of an asynchronous Serverless job. Returns the current status and output when complete. Job statuses: IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELLED, TIMED_OUT.",
2453
+ "Check the status of an asynchronous Serverless job. Returns the current status and output when complete. Job statuses: IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELLED, TIMED_OUT. IMPORTANT: a job stuck IN_QUEUE does not necessarily mean a capacity shortage \u2014 a worker may have spun up and crash-looped (the platform marks it UNHEALTHY while the job stays queued). When the job is IN_QUEUE this tool attaches a workerHealth summary and a hint; to dig deeper, call list-endpoint-workers (look for UNHEALTHY) and stream-worker-logs.",
2319
2454
  {
2320
2455
  endpointId: endpointIdSchema.describe(
2321
2456
  "ID of the Serverless endpoint the job belongs to"
@@ -2328,6 +2463,16 @@ function registerJobTools(server, rt) {
2328
2463
  params.endpointId,
2329
2464
  `/status/${params.jobId}`
2330
2465
  );
2466
+ if (result && typeof result === "object" && result.status === "IN_QUEUE") {
2467
+ const diagnosis = await diagnoseQueuedJob(params.endpointId);
2468
+ if (diagnosis) {
2469
+ return jsonReply2({
2470
+ ...result,
2471
+ workerHealth: diagnosis.workerHealth,
2472
+ ...diagnosis.hint ? { hint: diagnosis.hint } : {}
2473
+ });
2474
+ }
2475
+ }
2331
2476
  return jsonReply2(result);
2332
2477
  }
2333
2478
  );
@@ -2429,7 +2574,7 @@ function registerJobTools(server, rt) {
2429
2574
  );
2430
2575
  server.tool(
2431
2576
  "endpoint-health",
2432
- "Get the health and operational status of a Serverless endpoint, including worker counts and job statistics.",
2577
+ "Get the health and operational status of a Serverless endpoint, including worker counts and job statistics. Use this when jobs are stuck IN_QUEUE: unhealthy workers mean a crash-looping container (a worker failure \u2014 inspect with stream-worker-logs), while zero workers means the endpoint is waiting for GPU capacity.",
2433
2578
  {
2434
2579
  endpointId: endpointIdSchema.describe(
2435
2580
  "ID of the Serverless endpoint to check health for"