@runpod/mcp-server 4.3.0 → 4.5.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/stdio.mjs CHANGED
@@ -1,17 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  authedGraphqlBase,
4
+ hostedMcpBase,
4
5
  publicGraphqlBase,
5
6
  sdkBase,
6
7
  serverlessBase
7
- } from "./chunk-DDX42UTT.mjs";
8
+ } from "./chunk-4GF3CMSH.mjs";
8
9
 
9
10
  // src/stdio.ts
10
11
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11
12
 
12
13
  // src/server.ts
13
14
  var SERVER_NAME = "Runpod API Server";
14
- var SERVER_VERSION = true ? "4.3.0" : "dev";
15
+ var SERVER_VERSION = true ? "4.5.0" : "dev";
15
16
 
16
17
  // src/specgen/server.ts
17
18
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -344,6 +345,7 @@ function coerceScalars(value, schema, defs, depth = 0) {
344
345
  var generatedTools = [
345
346
  {
346
347
  name: "create-cluster",
348
+ title: "Create cluster",
347
349
  operationId: "createCluster",
348
350
  description: "Create an instant cluster (multiple pods with high-speed interconnect). BILLABLE from creation for every pod in the cluster: it rents compute.podCount identical pods at once, each with compute.gpuCountPerPod GPUs of compute.gpuTypeId \u2014 state the hourly price for the total GPU count (podCount * gpuCountPerPod, priced from the catalog tools) and create only after the user's explicit go on that quoted shape and total hourly price. Verify GPU stock first via the catalog tools. The compute shape and type are fixed at creation; only the name can be changed later. The container configuration applies to every pod, and templateId is the only private-image path \u2014 a bare registry property is rejected. Delete with delete-cluster when finished.",
349
351
  method: "POST",
@@ -581,6 +583,7 @@ var generatedTools = [
581
583
  },
582
584
  {
583
585
  name: "create-delegation",
586
+ title: "Create ECR delegation",
584
587
  operationId: "createDelegation",
585
588
  description: "Register an ECR delegation",
586
589
  method: "POST",
@@ -626,6 +629,7 @@ var generatedTools = [
626
629
  },
627
630
  {
628
631
  name: "create-endpoint",
632
+ title: "Create endpoint",
629
633
  operationId: "createEndpoint",
630
634
  description: "Create a serverless endpoint. BILLABLE: state the worker's hourly price (from the catalog tools) before creating. Worker hardware is one of two choices: the gpu object (gpu.pools takes serverless pool ids from the catalog, e.g. ADA_24, never display names; gpu.count sets GPUs per worker) or the cpu array of CPU flavor configurations for a CPU endpoint. type and scaling are required and fix the request-routing model. Workers cold-start on the first job: expect minutes, poll with get-job-status wait.",
631
635
  method: "POST",
@@ -1071,6 +1075,7 @@ var generatedTools = [
1071
1075
  },
1072
1076
  {
1073
1077
  name: "create-global-volume",
1078
+ title: "Create global volume",
1074
1079
  operationId: "createGlobalVolume",
1075
1080
  description: "Create a global volume. Provisions a new global volume \u2014 storage that mounts into workloads in any data center. Only a `name` is required; storage is provisioned for you. Accounts have a cap on how many global volumes they can hold; creating one past it fails with `400`. Returns `201` with the created global volume, including its assigned `id`.",
1076
1081
  method: "POST",
@@ -1112,6 +1117,7 @@ var generatedTools = [
1112
1117
  },
1113
1118
  {
1114
1119
  name: "create-network-volume",
1120
+ title: "Create network volume",
1115
1121
  operationId: "createNetworkVolume",
1116
1122
  description: "Create a network volume. Provisions a new network volume \u2014 persistent, network-attached storage that can be mounted into pods and serverless workers. Required inputs are `name`, `size` (in GB), and `dataCenter`; an optional `type` selects the storage tier and is immutable after creation. See `CreateNetworkVolumeRequest` for the size bounds and tier options. This creates a billable persistent resource that keeps incurring storage charges until it is deleted. Returns `201` with the created network volume, including its assigned `id`.",
1117
1123
  method: "POST",
@@ -1177,6 +1183,7 @@ var generatedTools = [
1177
1183
  },
1178
1184
  {
1179
1185
  name: "create-pod",
1186
+ title: "Create Pod",
1180
1187
  operationId: "createPod",
1181
1188
  description: 'Rent a GPU/CPU pod. BILLABLE from creation until stop/terminate: state the hourly price before creating. GPU selection is the gpu object: gpu.id takes a catalog GPU type id (e.g. "NVIDIA GeForce RTX 4090"), verified via list-gpu-types, and gpu.count sets the GPU count; cloud/data-center choice constrains stock.',
1182
1189
  method: "POST",
@@ -1544,6 +1551,7 @@ var generatedTools = [
1544
1551
  },
1545
1552
  {
1546
1553
  name: "create-registry",
1554
+ title: "Create registry credential",
1547
1555
  operationId: "createRegistry",
1548
1556
  description: "Create a container registry credential. Stores credentials for a private container registry. Credentials are write-only.",
1549
1557
  method: "POST",
@@ -1595,6 +1603,7 @@ var generatedTools = [
1595
1603
  },
1596
1604
  {
1597
1605
  name: "create-secret",
1606
+ title: "Create secret",
1598
1607
  operationId: "createSecret",
1599
1608
  description: "Create a secret. Stores a new account-scoped encrypted string. `name` must be unique across the account's secrets and is immutable; `value` is write-only and can never be read back through the API. Use the secret from pods, serverless endpoints, and templates by setting an environment variable's value to `{{ RUNPOD_SECRET_<name> }}` \u2014 Runpod substitutes the stored value when the pod or worker boots. Returns `201` with the created secret's metadata, or `409` when the name is already taken.",
1600
1609
  method: "POST",
@@ -1649,6 +1658,7 @@ var generatedTools = [
1649
1658
  },
1650
1659
  {
1651
1660
  name: "create-template",
1661
+ title: "Create template",
1652
1662
  operationId: "createTemplate",
1653
1663
  description: "Create a template. Creates a reusable container-configuration preset \u2014 image, disk, ports, env, registry, and mount settings \u2014 for pods and serverless endpoints. Pass its ID as `templateId` to `createPod` or `createEndpoint`, or spread its fields into the request body directly. Returns the created template.",
1654
1664
  method: "POST",
@@ -1850,6 +1860,7 @@ var generatedTools = [
1850
1860
  },
1851
1861
  {
1852
1862
  name: "delete-cluster",
1863
+ title: "Delete cluster",
1853
1864
  operationId: "deleteCluster",
1854
1865
  description: "Delete a cluster permanently: it terminates every member pod. Verify the id with get-cluster, and list-cluster-pods for what the delete takes down, before deleting; delete only clusters your own tool calls created in this conversation, or the one the user asked you to delete by id \u2014 a name found by listing is not attribution.",
1855
1866
  method: "DELETE",
@@ -1880,6 +1891,7 @@ var generatedTools = [
1880
1891
  },
1881
1892
  {
1882
1893
  name: "delete-endpoint",
1894
+ title: "Delete endpoint",
1883
1895
  operationId: "deleteEndpoint",
1884
1896
  description: "Delete a serverless endpoint permanently and immediately. Verify the id belongs to the intended endpoint (get-endpoint) before deleting; delete only endpoints your own tool calls created in this conversation, or the one the user asked you to delete by id \u2014 a name found by listing is not attribution.",
1885
1897
  method: "DELETE",
@@ -1910,6 +1922,7 @@ var generatedTools = [
1910
1922
  },
1911
1923
  {
1912
1924
  name: "delete-global-volume",
1925
+ title: "Delete global volume",
1913
1926
  operationId: "deleteGlobalVolume",
1914
1927
  description: "Delete a global volume. Permanently deletes a global volume and its stored data. Fails with `409` while the volume is still mounted: terminate every pod that mounts it (stopped pods count) and detach it from every serverless endpoint first. Nothing is detached or terminated on your behalf.",
1915
1928
  method: "DELETE",
@@ -1940,6 +1953,7 @@ var generatedTools = [
1940
1953
  },
1941
1954
  {
1942
1955
  name: "delete-network-volume",
1956
+ title: "Delete network volume",
1943
1957
  operationId: "deleteNetworkVolume",
1944
1958
  description: "Delete a network volume. Permanently deletes a network volume and releases its storage.",
1945
1959
  method: "DELETE",
@@ -1970,6 +1984,7 @@ var generatedTools = [
1970
1984
  },
1971
1985
  {
1972
1986
  name: "delete-pod",
1987
+ title: "Delete Pod",
1973
1988
  operationId: "deletePod",
1974
1989
  description: "Terminate a pod permanently \u2014 its container disk is lost. Distinguish from stopping (pod-action stop) which releases the GPU but keeps volume data. Verify the id with get-pod first; terminate only pods your own tool calls created in this conversation, or the one the user asked you to terminate by id \u2014 a name found by listing is not attribution.",
1975
1990
  method: "DELETE",
@@ -2000,6 +2015,7 @@ var generatedTools = [
2000
2015
  },
2001
2016
  {
2002
2017
  name: "delete-registry",
2018
+ title: "Delete registry credential",
2003
2019
  operationId: "deleteRegistry",
2004
2020
  description: "Delete a container registry credential. Permanently deletes a container registry credential by ID. Rejected if any pod currently uses this credential to pull its image. Templates that reference it are not part of that check \u2014 they silently lose the reference (`registry` becomes null) instead of blocking the delete.",
2005
2021
  method: "DELETE",
@@ -2029,6 +2045,7 @@ var generatedTools = [
2029
2045
  },
2030
2046
  {
2031
2047
  name: "delete-secret",
2048
+ title: "Delete secret",
2032
2049
  operationId: "deleteSecret",
2033
2050
  description: "Delete a secret permanently. Environment variables referencing its name ({{ RUNPOD_SECRET_<name> }}) stop resolving at the next pod or worker boot; running instances keep the value they started with. Verify the id with get-secret first; delete only secrets your own tool calls created in this conversation, or the one the user asked you to delete by id \u2014 a name found by listing is not attribution.",
2034
2051
  method: "DELETE",
@@ -2059,6 +2076,7 @@ var generatedTools = [
2059
2076
  },
2060
2077
  {
2061
2078
  name: "delete-template",
2079
+ title: "Delete template",
2062
2080
  operationId: "deleteTemplate",
2063
2081
  description: "Delete a template permanently. Verify with get-template first; a template referenced by a live endpoint should not be deleted.",
2064
2082
  method: "DELETE",
@@ -2088,6 +2106,7 @@ var generatedTools = [
2088
2106
  },
2089
2107
  {
2090
2108
  name: "get-cluster",
2109
+ title: "Get cluster",
2091
2110
  operationId: "getCluster",
2092
2111
  description: "Get a cluster. Returns a single cluster by ID. The pods field is an aggregate summary (total + count by status); fetch the member pods themselves from /v2/clusters/{id}/pods.",
2093
2112
  method: "GET",
@@ -2118,6 +2137,7 @@ var generatedTools = [
2118
2137
  },
2119
2138
  {
2120
2139
  name: "get-cpu-type",
2140
+ title: "Get CPU type",
2121
2141
  operationId: "getCpuType",
2122
2142
  description: "Get a CPU type. Returns a single CPU type with pricing. Availability details are included only when requested with include=AVAILABILITY, which requires `product` \u2014 stock differs by product context.",
2123
2143
  method: "GET",
@@ -2194,6 +2214,7 @@ var generatedTools = [
2194
2214
  },
2195
2215
  {
2196
2216
  name: "get-data-center",
2217
+ title: "Get data center",
2197
2218
  operationId: "getDataCenter",
2198
2219
  description: "Get a data center. Returns a single data center. Availability is included only when requested with include=GPU_AVAILABILITY or include=CPU_AVAILABILITY.",
2199
2220
  method: "GET",
@@ -2243,6 +2264,7 @@ var generatedTools = [
2243
2264
  },
2244
2265
  {
2245
2266
  name: "get-endpoint",
2267
+ title: "Get endpoint",
2246
2268
  operationId: "getEndpoint",
2247
2269
  description: "Get a serverless endpoint. Returns a single serverless endpoint by ID.",
2248
2270
  method: "GET",
@@ -2273,6 +2295,7 @@ var generatedTools = [
2273
2295
  },
2274
2296
  {
2275
2297
  name: "get-endpoint-build",
2298
+ title: "Get endpoint build",
2276
2299
  operationId: "getEndpointBuild",
2277
2300
  description: "Get a serverless endpoint build. Returns one of the endpoint's GitHub builds by id, regardless of age \u2014 no need to page through `GET /v2/serverless/{id}/builds` to reach it.",
2278
2301
  method: "GET",
@@ -2311,6 +2334,7 @@ var generatedTools = [
2311
2334
  },
2312
2335
  {
2313
2336
  name: "get-global-volume",
2337
+ title: "Get global volume",
2314
2338
  operationId: "getGlobalVolume",
2315
2339
  description: "Get a global volume. Returns a single global volume by ID.",
2316
2340
  method: "GET",
@@ -2341,6 +2365,7 @@ var generatedTools = [
2341
2365
  },
2342
2366
  {
2343
2367
  name: "get-gpu-type",
2368
+ title: "Get GPU type",
2344
2369
  operationId: "getGpuType",
2345
2370
  description: "Get a GPU type. Returns a single GPU type with pricing. Availability details are included only when requested with include=AVAILABILITY, which requires `product` \u2014 stock differs by product context.",
2346
2371
  method: "GET",
@@ -2465,6 +2490,7 @@ var generatedTools = [
2465
2490
  },
2466
2491
  {
2467
2492
  name: "get-network-volume",
2493
+ title: "Get network volume",
2468
2494
  operationId: "getNetworkVolume",
2469
2495
  description: "Get a network volume. Returns a single network volume by ID.",
2470
2496
  method: "GET",
@@ -2495,6 +2521,7 @@ var generatedTools = [
2495
2521
  },
2496
2522
  {
2497
2523
  name: "get-organization",
2524
+ title: "Get organization",
2498
2525
  operationId: "getOrganization",
2499
2526
  description: "Get your current organization. Returns the authenticated caller's current organization. Organization context comes from the caller's membership; no organization selector is required. Returns 404 when the caller does not belong to an organization, or 403 when organizations are unavailable to the caller.",
2500
2527
  method: "GET",
@@ -2514,6 +2541,7 @@ var generatedTools = [
2514
2541
  },
2515
2542
  {
2516
2543
  name: "get-pod",
2544
+ title: "Get Pod",
2517
2545
  operationId: "getPod",
2518
2546
  description: "Get a pod. Returns a single pod by ID.",
2519
2547
  method: "GET",
@@ -2544,6 +2572,7 @@ var generatedTools = [
2544
2572
  },
2545
2573
  {
2546
2574
  name: "get-registry",
2575
+ title: "Get registry credential",
2547
2576
  operationId: "getRegistry",
2548
2577
  description: "Get a container registry credential. Returns a single container registry credential by ID. `username` and `password` are never included in the response \u2014 credentials are write-only, matching `createRegistry`.",
2549
2578
  method: "GET",
@@ -2573,6 +2602,7 @@ var generatedTools = [
2573
2602
  },
2574
2603
  {
2575
2604
  name: "get-secret",
2605
+ title: "Get secret",
2576
2606
  operationId: "getSecret",
2577
2607
  description: "Get a secret. Returns a single secret's metadata by ID. The value is write-only and never returned.",
2578
2608
  method: "GET",
@@ -2603,6 +2633,7 @@ var generatedTools = [
2603
2633
  },
2604
2634
  {
2605
2635
  name: "get-ssh-keys",
2636
+ title: "Get SSH keys",
2606
2637
  operationId: "getSshKeys",
2607
2638
  description: "List registered SSH public keys. Returns the account's registered SSH public keys \u2014 the keys provisioned into pods created with `startSsh` and used to authenticate the SSH connections reported in a pod's `ssh` block.",
2608
2639
  method: "GET",
@@ -2622,6 +2653,7 @@ var generatedTools = [
2622
2653
  },
2623
2654
  {
2624
2655
  name: "get-template",
2656
+ title: "Get template",
2625
2657
  operationId: "getTemplate",
2626
2658
  description: "Get a template. Returns the full configuration of a single template by ID. Serves both templates you own and public catalog templates \u2014 everything you can read. Updates and deletes remain restricted to templates you own.",
2627
2659
  method: "GET",
@@ -2651,6 +2683,7 @@ var generatedTools = [
2651
2683
  },
2652
2684
  {
2653
2685
  name: "list-billing",
2686
+ title: "List billing",
2654
2687
  operationId: "listBilling",
2655
2688
  description: "Get aggregated billing history. Returns time-bucketed total spend across all billable Runpod resources for the authenticated user. Use startTime/endTime with bucketSize for an explicit range, or lastN with bucketSize for the most recent buckets. Each record reports one bucket's total plus pod, serverless, storage, public endpoint, and Instant Cluster cost components. The metadata block echoes the resolved query window, record count, and totals across all returned buckets.",
2656
2689
  method: "GET",
@@ -2723,6 +2756,7 @@ var generatedTools = [
2723
2756
  },
2724
2757
  {
2725
2758
  name: "list-cluster-billing",
2759
+ title: "List cluster billing",
2726
2760
  operationId: "listClusterBilling",
2727
2761
  description: "Get cluster billing history. Returns Cluster billing history for the authenticated user, split into time buckets by startTime/endTime with bucketSize or by lastN recent buckets. Use clusterId to filter to one cluster; without it, records are emitted per cluster per bucket. Each record includes GPU compute, disk, inter-node networking, and total amounts. Clusters are GPU-only, so no CPU cost component is returned.",
2728
2762
  method: "GET",
@@ -2803,6 +2837,7 @@ var generatedTools = [
2803
2837
  },
2804
2838
  {
2805
2839
  name: "list-cluster-pods",
2840
+ title: "List cluster Pods",
2806
2841
  operationId: "listClusterPods",
2807
2842
  description: "List a cluster's pods. Returns the full member pods of a cluster. The cluster summary (`GET /v2/clusters/{id}`) carries only aggregate pod counts; this endpoint returns each member as a complete Pod object.",
2808
2843
  method: "GET",
@@ -2833,6 +2868,7 @@ var generatedTools = [
2833
2868
  },
2834
2869
  {
2835
2870
  name: "list-clusters",
2871
+ title: "List clusters",
2836
2872
  operationId: "listClusters",
2837
2873
  description: "List clusters. Returns clusters owned by the authenticated user, cursor-paginated newest-first; an omitted `limit` defaults to 1000. Follow `pagination.nextCursor` until `hasNextPage` is false.",
2838
2874
  method: "GET",
@@ -2873,6 +2909,7 @@ var generatedTools = [
2873
2909
  },
2874
2910
  {
2875
2911
  name: "list-cpu-types",
2912
+ title: "List CPU types",
2876
2913
  operationId: "listCpuTypes",
2877
2914
  description: "List CPU types. Returns available CPU flavors. Availability is included only when requested with include=AVAILABILITY, which requires `product` \u2014 stock differs by product context.",
2878
2915
  method: "GET",
@@ -2941,6 +2978,7 @@ var generatedTools = [
2941
2978
  },
2942
2979
  {
2943
2980
  name: "list-data-centers",
2981
+ title: "List data centers",
2944
2982
  operationId: "listDataCenters",
2945
2983
  description: "List data centers. Returns available data center locations with region, compliance, supported network volume tiers, and global networking support. Use include=GPU_AVAILABILITY or include=CPU_AVAILABILITY to add per-resource availability arrays to each data center. The regions, networkVolumeTypes, compliance, and globalNetwork query parameters filter the list before it is returned.",
2946
2984
  method: "GET",
@@ -3068,6 +3106,7 @@ var generatedTools = [
3068
3106
  },
3069
3107
  {
3070
3108
  name: "list-delegations",
3109
+ title: "List ECR delegations",
3071
3110
  operationId: "listDelegations",
3072
3111
  description: "List all ECR delegations",
3073
3112
  method: "GET",
@@ -3087,6 +3126,7 @@ var generatedTools = [
3087
3126
  },
3088
3127
  {
3089
3128
  name: "list-endpoint-billing",
3129
+ title: "List endpoint billing",
3090
3130
  operationId: "listEndpointBilling",
3091
3131
  description: "Get public endpoint billing history. Returns Runpod public endpoint billing history for the authenticated user, split into time buckets by startTime/endTime with bucketSize or by lastN recent buckets. Each record reports the endpoint total for one bucket, and metadata echoes the resolved query window, record count, and total endpoint amount across all returned records.",
3092
3132
  method: "GET",
@@ -3159,6 +3199,7 @@ var generatedTools = [
3159
3199
  },
3160
3200
  {
3161
3201
  name: "list-endpoint-builds",
3202
+ title: "List endpoint builds",
3162
3203
  operationId: "listEndpointBuilds",
3163
3204
  description: "List serverless endpoint builds. Returns the endpoint's GitHub build history, newest first (Runpod GitHub-build integration), cursor-paginated; an omitted `limit` defaults to 100, so a bare request returns at most the 100 most recent builds. Any build can also be fetched by id via `GET /v2/serverless/{id}/builds/{buildId}`. Stream a build's logs via `/v2/serverless/{id}/builds/{buildId}/logs`.",
3164
3205
  method: "GET",
@@ -3208,6 +3249,7 @@ var generatedTools = [
3208
3249
  },
3209
3250
  {
3210
3251
  name: "list-endpoint-releases",
3252
+ title: "List endpoint releases",
3211
3253
  operationId: "listEndpointReleases",
3212
3254
  description: "List serverless endpoint releases. Returns the endpoint's release history (newest first) plus a rollout summary of how many workers are running the current version. Each release is a versioned configuration snapshot with a `diff` of what changed; build-driven releases carry a `buildId` (fetch build detail via the builds sub-routes). Releases are cursor-paginated newest-first; an omitted `limit` defaults to 1000. The rollout summary always describes the endpoint's current state, independent of the page requested.",
3213
3255
  method: "GET",
@@ -3257,6 +3299,7 @@ var generatedTools = [
3257
3299
  },
3258
3300
  {
3259
3301
  name: "list-endpoint-workers",
3302
+ title: "List endpoint workers",
3260
3303
  operationId: "listEndpointWorkers",
3261
3304
  description: "List serverless endpoint workers. Lists the active workers for a serverless endpoint. **Returns.** A `200` with a `ListEndpointWorkersResponse`: a `workers` array (one entry per active worker, each carrying its `id`, `status`, and runtime details) plus a `summary` of worker counts grouped by status. Only currently active workers are included; scaled-down workers are not returned. **How `status` is determined.** Each worker's `status` is derived by reconciling the worker pod's lifecycle status with the endpoint's live job-queue view (which workers are actively serving requests). When the job-queue view is unavailable, the response degrades gracefully: the shape is unchanged, but each `status` and the summary counts fall back to pod lifecycle alone.",
3262
3305
  method: "GET",
@@ -3287,6 +3330,7 @@ var generatedTools = [
3287
3330
  },
3288
3331
  {
3289
3332
  name: "list-global-volumes",
3333
+ title: "List global volumes",
3290
3334
  operationId: "listGlobalVolumes",
3291
3335
  description: "List global volumes. Returns global volumes owned by the authenticated user, cursor-paginated newest-first; an omitted `limit` defaults to 1000. Follow `pagination.nextCursor` until `hasNextPage` is false.",
3292
3336
  method: "GET",
@@ -3327,6 +3371,7 @@ var generatedTools = [
3327
3371
  },
3328
3372
  {
3329
3373
  name: "list-gpu-types",
3374
+ title: "List GPU types",
3330
3375
  operationId: "listGpuTypes",
3331
3376
  description: 'List GPU types. Returns available GPU types with pricing. Availability is included only when requested with include=AVAILABILITY, which requires `product` \u2014 stock differs by product context. With countryCodes, the list is narrowed to GPU types deployable in those countries, so "this geography + this chip" resolves in one read.',
3332
3377
  method: "GET",
@@ -3443,6 +3488,7 @@ var generatedTools = [
3443
3488
  },
3444
3489
  {
3445
3490
  name: "list-network-volume-billing",
3491
+ title: "List network volume billing",
3446
3492
  operationId: "listNetworkVolumeBilling",
3447
3493
  description: "Get network volume billing history. Returns network volume billing history for the authenticated user, split into time buckets by startTime/endTime with bucketSize or by lastN recent buckets. Use networkVolumeId to filter to one volume; without it, records are emitted per volume per bucket. Each record includes standard storage, high-performance storage, and total amounts, while metadata reports the resolved query, distinct volume count, and totals across the returned records.",
3448
3494
  method: "GET",
@@ -3523,6 +3569,7 @@ var generatedTools = [
3523
3569
  },
3524
3570
  {
3525
3571
  name: "list-network-volumes",
3572
+ title: "List network volumes",
3526
3573
  operationId: "listNetworkVolumes",
3527
3574
  description: "List network volumes. Returns network volumes owned by the authenticated user, cursor-paginated newest-first; an omitted `limit` defaults to 1000. Follow `pagination.nextCursor` until `hasNextPage` is false.",
3528
3575
  method: "GET",
@@ -3563,6 +3610,7 @@ var generatedTools = [
3563
3610
  },
3564
3611
  {
3565
3612
  name: "list-pod-billing",
3613
+ title: "List Pod billing",
3566
3614
  operationId: "listPodBilling",
3567
3615
  description: "Get pod billing history. Returns pod-only billing detail for the authenticated user, split into time buckets by startTime/endTime with bucketSize or by lastN recent buckets. Use podId to narrow the response to one GPU or CPU pod; without it, records are emitted per pod per bucket. Each record includes podId, GPU, CPU, disk, and total amounts, while metadata echoes the resolved query and totals across the pod records. Use listBilling when you need aggregate spend across every billable resource family.",
3568
3616
  method: "GET",
@@ -3643,6 +3691,7 @@ var generatedTools = [
3643
3691
  },
3644
3692
  {
3645
3693
  name: "list-pods",
3694
+ title: "List Pods",
3646
3695
  operationId: "listPods",
3647
3696
  description: "List pods. Returns pods owned by the authenticated user. Cluster member pods are excluded by default; set `includeClusterPods=true` to include them (each carries a non-null `cluster` membership block). Results are cursor-paginated newest-first; an omitted `limit` defaults to 1000. The cluster-member exclusion is applied before the page is cut, so a page holds `limit` pods whenever that many remain. Follow `pagination.nextCursor` until `hasNextPage` is false.",
3648
3697
  method: "GET",
@@ -3692,6 +3741,7 @@ var generatedTools = [
3692
3741
  },
3693
3742
  {
3694
3743
  name: "list-public-templates",
3744
+ title: "List public templates",
3695
3745
  operationId: "listPublicTemplates",
3696
3746
  description: "List public templates. Returns the public template catalog. `source` selects which slice: `official` (the default) is Runpod-curated templates, `verified` is community templates Runpod has verified, and `community` is everything else other users have shared publicly. Both pod and serverless templates appear \u2014 use each entry's `serverless` flag to tell them apart. `registry` is always null for templates you don't own. Your own templates (public or private) are managed under `/v2/templates`; fetch any individual template \u2014 catalog or owned \u2014 via `/v2/templates/{id}`. At most 100 templates are returned. Cursor pagination is not yet supported here; `pagination` is always the exhausted marker (`nextCursor: null`, `hasNextPage: false`).",
3697
3747
  method: "GET",
@@ -3723,6 +3773,7 @@ var generatedTools = [
3723
3773
  },
3724
3774
  {
3725
3775
  name: "list-registries",
3776
+ title: "List registry credentials",
3726
3777
  operationId: "listRegistries",
3727
3778
  description: "List container registries. Returns all container registry credentials owned by the authenticated user.",
3728
3779
  method: "GET",
@@ -3742,6 +3793,7 @@ var generatedTools = [
3742
3793
  },
3743
3794
  {
3744
3795
  name: "list-secrets",
3796
+ title: "List secrets",
3745
3797
  operationId: "listSecrets",
3746
3798
  description: "List secrets. Returns the account's secrets \u2014 encrypted strings referenced from pod, serverless, and template environment variables with the `{{ RUNPOD_SECRET_<name> }}` placeholder syntax, substituted with the secret's value when the pod or worker boots. Secret values are write-only and are never returned.",
3747
3799
  method: "GET",
@@ -3771,6 +3823,7 @@ var generatedTools = [
3771
3823
  },
3772
3824
  {
3773
3825
  name: "list-serverless-billing",
3826
+ title: "List Serverless billing",
3774
3827
  operationId: "listServerlessBilling",
3775
3828
  description: "Get serverless billing history, split into time buckets. SIZE WARNING: with defaults this emits one record per endpoint PER BUCKET \u2014 on an account with many endpoints the response overflows the tool-result limit and becomes unusable. Always pass lastN: 1 (or a bucketSize spanning the whole window) for one record per endpoint, and serverlessId when you need one endpoint. Distinct from pod billing, which covers standalone pods.",
3776
3829
  method: "GET",
@@ -3851,6 +3904,7 @@ var generatedTools = [
3851
3904
  },
3852
3905
  {
3853
3906
  name: "pod-action",
3907
+ title: "Start, stop, restart, or terminate a Pod",
3854
3908
  operationId: "podAction",
3855
3909
  description: "Trigger a pod state transition. Triggers a state transition on a pod. Send a JSON body with a single `action` field, e.g. `{ \"action\": \"stop\" }`. Valid actions: - `start` \u2014 boot a stopped pod (`EXITED` or `ERROR`) back toward `RUNNING`. - `stop` \u2014 stop a running or provisioning pod, releasing GPU/CPU compute while keeping its disk. The pod moves to `EXITED`. - `restart` \u2014 restart a `RUNNING` pod's container in place. - `terminate` \u2014 permanently delete the pod and release its resources (equivalent to `deletePod`). Which actions are valid depends on the pod's current status, and the currently permitted set is published in the pod's `actions` field: `RUNNING` allows `stop`/`restart`/`terminate`; `EXITED` and `ERROR` allow `start`/`terminate`; `PROVISIONING` and `STARTING` allow `stop`/`terminate`. `start`, `stop`, and `restart` return `200` with the updated pod. `terminate` returns `204` with no body. Requesting an action that is not valid for the pod's current status returns `409`.",
3856
3910
  method: "POST",
@@ -3900,6 +3954,7 @@ var generatedTools = [
3900
3954
  },
3901
3955
  {
3902
3956
  name: "revoke-delegation",
3957
+ title: "Revoke ECR delegation",
3903
3958
  operationId: "revokeDelegation",
3904
3959
  description: "Revoke an ECR delegation",
3905
3960
  method: "DELETE",
@@ -3929,6 +3984,7 @@ var generatedTools = [
3929
3984
  },
3930
3985
  {
3931
3986
  name: "update-cluster",
3987
+ title: "Update cluster",
3932
3988
  operationId: "updateCluster",
3933
3989
  description: "Rename a cluster. Renames a cluster. This endpoint only changes the cluster name \u2014 compute shape, type, and container configuration are fixed at creation and cannot be updated.",
3934
3990
  method: "PATCH",
@@ -3977,6 +4033,7 @@ var generatedTools = [
3977
4033
  },
3978
4034
  {
3979
4035
  name: "update-endpoint",
4036
+ title: "Update endpoint",
3980
4037
  operationId: "updateEndpoint",
3981
4038
  description: "Partially update a serverless endpoint (PATCH): only fields present in the body change, omitted ones are untouched, with one exception. DESTRUCTIVE GOTCHA: gpu.pools and gpu.excludedTypes are a single selection, so sending pools without excludedTypes clears any exclusions pinned elsewhere (the console or set-endpoint-gpus) \u2014 read the endpoint first with get-endpoint and resend excludedTypes if you still want them. An exclusion naming a type outside pools returns 400. A CUDA-only or count-only change does not need pools at all, so omit it unless you mean to replace the selection. Mutable fields: name, gpu, cpu, workers (min/max/idleTimeout), scaling (type plus queueDelay or requestCount), dataCenterIds, networkVolumes, timeout, flashboot, and the container settings (image, args, disk, ports, env, registry). cpu completely replaces a CPU endpoint's flavor selection. Compute family is immutable: gpu on a CPU endpoint, cpu on a GPU one, or both, returns 400. Effect timing differs by field \u2014 workers/scaling/timeout reach the autoscaler promptly, while container changes (image, env) cut a new release that rolls out as workers cycle, so in-flight workers keep the previous version. Track rollout with list-endpoint-releases.",
3982
4039
  method: "PATCH",
@@ -4367,6 +4424,7 @@ var generatedTools = [
4367
4424
  },
4368
4425
  {
4369
4426
  name: "update-global-volume",
4427
+ title: "Update global volume",
4370
4428
  operationId: "updateGlobalVolume",
4371
4429
  description: "Update a global volume. Updates mutable fields on a global volume. Only provided fields are changed.",
4372
4430
  method: "PATCH",
@@ -4418,6 +4476,7 @@ var generatedTools = [
4418
4476
  },
4419
4477
  {
4420
4478
  name: "update-network-volume",
4479
+ title: "Update network volume",
4421
4480
  operationId: "updateNetworkVolume",
4422
4481
  description: "Update a network volume. Updates mutable fields on a network volume. Only provided fields are changed. Note: `size` may only increase; attempts to reduce size will be rejected.",
4423
4482
  method: "PATCH",
@@ -4474,6 +4533,7 @@ var generatedTools = [
4474
4533
  },
4475
4534
  {
4476
4535
  name: "update-pod",
4536
+ title: "Update Pod",
4477
4537
  operationId: "updatePod",
4478
4538
  description: "Update a pod. Partially updates a pod's configuration. This is a PATCH: only the fields present in the body are changed, and omitted fields are left untouched. Use empty values only when you explicitly mean to clear a field (for example, set `registry` to `null` or set `ports` to `[]`). See `UpdatePodRequest` for the full body. Mutable fields: `name`, `image`, `args`, `disk`, `ports`, `env`, `registry`, `mounts`, `locked`, and `globalNetworking`. Some changes apply immediately while others (e.g. `globalNetworking`) take effect on the pod's next start/restart, as noted on the individual fields. Pods that belong to a Cluster cannot be updated here \u2014 manage them through `/v2/clusters/{id}`. Returns `200` with the full updated pod.",
4479
4539
  method: "PATCH",
@@ -4715,6 +4775,7 @@ var generatedTools = [
4715
4775
  },
4716
4776
  {
4717
4777
  name: "update-secret",
4778
+ title: "Update secret",
4718
4779
  operationId: "updateSecret",
4719
4780
  description: "Update a secret. Rotates a secret's value and/or updates its description. Only the provided fields are changed and at least one field is required; the `name` is immutable. Pods and workers receive the new value at their next boot \u2014 running instances keep the value they were started with. When both fields are sent, the value is applied first, then the description. The two updates are not atomic: if the description update fails after the value was rotated, the response is an error but the new value has already taken effect. Send the fields in separate requests when that partial outcome matters.",
4720
4781
  method: "PATCH",
@@ -4770,6 +4831,7 @@ var generatedTools = [
4770
4831
  },
4771
4832
  {
4772
4833
  name: "update-ssh-keys",
4834
+ title: "Update SSH keys",
4773
4835
  operationId: "updateSshKeys",
4774
4836
  description: "Replace registered SSH public keys. Replaces the account's full set of registered SSH public keys. Existing keys not present in the request are removed; send `[]` to remove all keys. Keys take effect for pods created afterwards with `startSsh` \u2014 running pods are not updated.",
4775
4837
  method: "PUT",
@@ -4816,6 +4878,7 @@ var generatedTools = [
4816
4878
  },
4817
4879
  {
4818
4880
  name: "update-template",
4881
+ title: "Update template",
4819
4882
  operationId: "updateTemplate",
4820
4883
  description: "Update a template. Partially updates a template. This is a PATCH: only the fields present in the body are changed; omitted fields are left untouched. See `UpdateTemplateRequest` for the full body. Mutable fields: `name`, `image`, `args`, `disk`, `ports`, `env`, `registry`, `mounts`, `serverless`, `public`, and `category`. Only the template's owner can update it (authenticated via the request's API key); public catalog templates are readable via GET but return `404` here. Returns `200` with the full updated template. Pods and endpoints already created from this template are not changed retroactively \u2014 the template is a snapshot applied at creation time.",
4821
4884
  method: "PATCH",
@@ -5114,6 +5177,43 @@ function isTimeout(error) {
5114
5177
  const cause = error.cause;
5115
5178
  return cause instanceof Error && (cause.name === "TimeoutError" || cause.name === "AbortError");
5116
5179
  }
5180
+ var UNREACHED_CODES = /* @__PURE__ */ new Set([
5181
+ "ENOTFOUND",
5182
+ "EAI_AGAIN",
5183
+ "ECONNREFUSED",
5184
+ "EHOSTUNREACH",
5185
+ "ENETUNREACH",
5186
+ "UND_ERR_CONNECT_TIMEOUT"
5187
+ ]);
5188
+ var TLS_CODE = /^(?:CERT_|ERR_TLS_|UNABLE_TO_|DEPTH_ZERO_|SELF_SIGNED_)/;
5189
+ function causeCode(cause) {
5190
+ const code = cause?.code;
5191
+ if (typeof code === "string") return code;
5192
+ const inner = cause instanceof AggregateError ? cause.errors[0] : void 0;
5193
+ const innerCode = inner?.code;
5194
+ return typeof innerCode === "string" ? innerCode : void 0;
5195
+ }
5196
+ function networkFailureResult(error) {
5197
+ if (!(error instanceof TypeError)) return void 0;
5198
+ const afterHeaders = error.message === "terminated";
5199
+ if (!afterHeaders && error.message !== "fetch failed") return void 0;
5200
+ const code = causeCode(error.cause);
5201
+ if (!code) return void 0;
5202
+ const unreached = !afterHeaders && (UNREACHED_CODES.has(code) || TLS_CODE.test(code));
5203
+ return {
5204
+ ok: false,
5205
+ status: unreached ? 503 : 502,
5206
+ payload: unreached ? {
5207
+ error: "Could not reach the Runpod API.",
5208
+ cause: code,
5209
+ hint: "The request never reached Runpod, so retrying the same call is safe: retry it once. If it fails again, the network, DNS, proxy or TLS setup on this machine is the likely cause; check https://uptime.runpod.io and report it rather than retrying further."
5210
+ } : {
5211
+ error: "The connection to the Runpod API dropped before the full response arrived.",
5212
+ cause: code,
5213
+ hint: "The request may have been applied. A read can be retried once. For a create, update, delete or job submission, check with the matching get- or list- tool whether it took effect before calling again; retrying blindly can create a duplicate billable resource."
5214
+ }
5215
+ };
5216
+ }
5117
5217
  async function runTool(body) {
5118
5218
  try {
5119
5219
  return await body();
@@ -5135,6 +5235,8 @@ async function runTool(body) {
5135
5235
  }
5136
5236
  };
5137
5237
  }
5238
+ const network = networkFailureResult(error);
5239
+ if (network) return network;
5138
5240
  throw error;
5139
5241
  }
5140
5242
  }
@@ -5205,6 +5307,7 @@ function rankByStock(rows, stats, includeUnavailable) {
5205
5307
  }
5206
5308
  var getCapacity = {
5207
5309
  name: "get-capacity",
5310
+ title: "Get GPU capacity by CUDA version",
5208
5311
  annotations: readOnly,
5209
5312
  description: "GPU capacity across host CUDA versions, as a matrix. Use this to choose an endpoint's allowedCudaVersions/minCudaVersion (or diagnose/widen a capacity-starved one) and to distinguish capacity problems from compatibility problems. Default mode is one call returning, per GPU type, overall stock plus AVAILABLE/UNAVAILABLE per host-reported CUDA version. Pass cudaVersions to deep-probe instead: one stock lookup per listed version, returning graded stock (High/Medium/Low/Out) and the lowest on-demand price per version; a probe that fails transiently reports a per-version error instead of failing the call. Nothing is hidden by default \u2014 set includeUnavailable:false to drop GPUs with no stock on any listed version. Credential-free public catalog data. Stock is live, so page cursors can shift between calls.",
5210
5313
  inputSchema: {
@@ -5406,6 +5509,7 @@ function formatGpuIds(gpu) {
5406
5509
  }
5407
5510
  var setEndpointGpus = {
5408
5511
  name: "set-endpoint-gpus",
5512
+ title: "Set endpoint GPUs",
5409
5513
  annotations: idempotentWrite,
5410
5514
  description: "Set which GPUs a Serverless endpoint's workers run on \u2014 including pinning specific GPU SKUs. Provide either a raw gpuIds string, or pools plus optional excludeGpuTypeIds (GPU type ids from list-gpu-types) and the exclusion string is built for you: a pool allows every SKU in it, and excluding all but one SKU pins that SKU exactly. Updates only GPU settings through REST v2, preserving other endpoint settings. create-endpoint/update-endpoint also support gpu.excludedTypes.",
5411
5515
  inputSchema: {
@@ -5600,6 +5704,7 @@ function randomSuffix() {
5600
5704
  }
5601
5705
  var listHubRepos = {
5602
5706
  name: "list-hub-repos",
5707
+ title: "List Hub repos",
5603
5708
  annotations: readOnly,
5604
5709
  description: "List repos published to the Runpod Hub (prebuilt Serverless workers and Pod templates, e.g. vLLM, ComfyUI). Public catalog \u2014 no auth required. Each result includes the currently listed release with its hubReleaseId and prebuilt image name. Results are sorted by deploy count (most popular first). Set includeConfig:true to also return the release config (hardware requirements and environment-variable schema) \u2014 it is large, so prefer requesting it for a single repo via the repoOwner/searchTerm filters.",
5605
5710
  inputSchema: {
@@ -5702,6 +5807,9 @@ var listHubRepos = {
5702
5807
  };
5703
5808
  var deployHubRepo = {
5704
5809
  name: "deploy-hub-repo",
5810
+ title: "Deploy Hub repo",
5811
+ // Two sequential upstream calls: together they can outlast a client's request timeout.
5812
+ holdsResponse: true,
5705
5813
  annotations: write,
5706
5814
  description: "Deploy a Runpod Hub repo's listed release as a new Serverless endpoint (the same as clicking Deploy on the Hub). Identify the repo by `repo` (\"owner/name\" from list-hub-repos) or by `hubReleaseId`. The release supplies the prebuilt image, container disk, CUDA constraints, and env-var defaults; pass `env` to override or fill in values (required keys without a default must be provided \u2014 check list-hub-repos with includeConfig:true for the schema). GPU selection comes from the release config when it specifies one; otherwise pass gpuIds (GPU pool names, e.g. 'ADA_24' or 'ADA_80_PRO,AMPERE_80'). Uses the authenticated GraphQL API (no REST home for Hub deploys yet).",
5707
5815
  inputSchema: {
@@ -6160,6 +6268,7 @@ function validateWait(wait) {
6160
6268
  }
6161
6269
  var runEndpoint = {
6162
6270
  name: "run-endpoint",
6271
+ title: "Submit Serverless job",
6163
6272
  annotations: write,
6164
6273
  description: "Submit an asynchronous job to a Serverless endpoint. Returns a job ID immediately \u2014 use get-job-status to poll for results (pass its `wait` to block through a cold start instead of tight-looping). Async results are available for 30 minutes after completion.",
6165
6274
  inputSchema: {
@@ -6190,6 +6299,8 @@ var runEndpoint = {
6190
6299
  };
6191
6300
  var runsyncEndpoint = {
6192
6301
  name: "runsync-endpoint",
6302
+ title: "Run Serverless job and wait",
6303
+ holdsResponse: true,
6193
6304
  annotations: write,
6194
6305
  description: "Submit a synchronous job to a Serverless endpoint and wait for the result. Best for fast tasks: if the job outlives the wait, the response returns a job ID and a non-terminal status (IN_QUEUE or IN_PROGRESS) to poll with get-job-status. Max payload 20 MB; results expire after 1 minute. The wait parameter extends the server-side wait" + (HOSTED ? ` \u2014 this server runs behind a 60-second gateway deadline, so waits clamp to ${HTTP_LONG_POLL_BUDGET_MS} ms, which is also the effective default. For longer jobs use run-endpoint + get-job-status.` : " to up to 5 minutes (300000 ms); it defaults to 90000 (90 seconds)."),
6195
6306
  inputSchema: {
@@ -6234,6 +6345,8 @@ var runsyncEndpoint = {
6234
6345
  };
6235
6346
  var getJobStatus = {
6236
6347
  name: "get-job-status",
6348
+ title: "Get job status",
6349
+ holdsResponse: true,
6237
6350
  annotations: readOnly,
6238
6351
  description: "Check the status of a Serverless job. Returns the current status and output when complete. Job statuses: IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELLED, TIMED_OUT. Pass `wait` (milliseconds, up to " + STATUS_WAIT_MAX_MS + ") to BLOCK server-side, polling until the job reaches a terminal status or the budget expires \u2014 use it to ride out a cold start (a first job on a fresh worker pulls the image and loads the model, commonly 1\u20135+ minutes) in a single call instead of returning on the first IN_QUEUE and tight-looping the tool. Without `wait` it returns the current status immediately (a single check). If the budget expires before a terminal status, it returns the latest non-terminal status with pollingTimedOut:true \u2014 call again (with `wait`) to keep blocking. IMPORTANT: a job stuck IN_QUEUE does not necessarily mean a capacity shortage \u2014 a worker may have spun up and crash-looped. When the job is IN_QUEUE this tool attaches a workerHealth summary and a hint. To dig deeper call list-endpoint-workers, then read stream-worker-logs regardless of the status a worker reports: a container failing to start loops while its worker still shows RUNNING or THROTTLED and unhealthy stays 0, so UNHEALTHY is one symptom of a crash loop and not a test for it. The logs are the authority.",
6239
6352
  inputSchema: {
@@ -6282,6 +6395,8 @@ var getJobStatus = {
6282
6395
  };
6283
6396
  var streamJob = {
6284
6397
  name: "stream-job",
6398
+ title: "Stream job output",
6399
+ holdsResponse: true,
6285
6400
  annotations: readOnly,
6286
6401
  description: `Retrieve streaming output from a Serverless job. The worker must support streaming output. Polls /stream/{jobId} and collects chunks until the status is COMPLETED, FAILED, CANCELLED, or TIMED_OUT, for up to ${formatBudget(STREAM_BUDGET_MS)}. If the budget expires first, returns the chunks collected so far with pollingTimedOut: true \u2014 call stream-job again to resume where it left off, or get-job-status to check without streaming.`,
6287
6402
  inputSchema: {
@@ -6313,6 +6428,7 @@ var streamJob = {
6313
6428
  };
6314
6429
  var cancelJob = {
6315
6430
  name: "cancel-job",
6431
+ title: "Cancel job",
6316
6432
  annotations: destructive,
6317
6433
  description: "Cancel a Serverless job that is queued or in progress.",
6318
6434
  inputSchema: {
@@ -6338,6 +6454,7 @@ var cancelJob = {
6338
6454
  };
6339
6455
  var retryJob = {
6340
6456
  name: "retry-job",
6457
+ title: "Retry job",
6341
6458
  annotations: write,
6342
6459
  description: "Retry a failed or timed-out Serverless job. Only works for jobs with FAILED or TIMED_OUT status. The previous output is removed and the job is requeued.",
6343
6460
  inputSchema: {
@@ -6363,6 +6480,7 @@ var retryJob = {
6363
6480
  };
6364
6481
  var endpointHealth = {
6365
6482
  name: "endpoint-health",
6483
+ title: "Get endpoint health",
6366
6484
  annotations: readOnly,
6367
6485
  description: "Get the endpoint-level health rollup for a Serverless endpoint: worker counts by state plus job queue statistics. This is the runtime plane's own /health view and it can lag or disagree with the per-worker truth \u2014 when a job is stuck IN_QUEUE, prefer list-endpoint-workers over this rollup for per-worker state \u2014 but treat neither as the authority on crash loops, because a container failing to start loops while its worker reports RUNNING or THROTTLED and unhealthy stays 0; stream-worker-logs is what settles it, and note that get-job-status already attaches that worker summary and a hint on every IN_QUEUE result. Zero workers here means the endpoint is waiting for GPU capacity.",
6368
6486
  inputSchema: {
@@ -6383,6 +6501,7 @@ var endpointHealth = {
6383
6501
  };
6384
6502
  var purgeEndpointQueue = {
6385
6503
  name: "purge-endpoint-queue",
6504
+ title: "Purge endpoint queue",
6386
6505
  annotations: destructive,
6387
6506
  description: "Remove all pending jobs from a Serverless endpoint queue. Only affects queued jobs \u2014 in-progress jobs continue running. Use this for error recovery or clearing outdated requests.",
6388
6507
  inputSchema: {
@@ -6419,6 +6538,7 @@ var jobTools = [
6419
6538
  // src/specgen/tools/list-endpoints.ts
6420
6539
  var listEndpoints = {
6421
6540
  name: "list-endpoints",
6541
+ title: "List endpoints",
6422
6542
  annotations: readOnly,
6423
6543
  description: "List serverless endpoints owned by the account (trimmed: id, name, image, compute config, worker counts, scaling, data centers per endpoint \u2014 env vars and request URLs are omitted; use get-endpoint for the full object). Returns a page; pass cursor=nextCursor for more.",
6424
6544
  inputSchema: {
@@ -6464,6 +6584,7 @@ var listEndpoints = {
6464
6584
  // src/specgen/tools/list-templates.ts
6465
6585
  var listTemplates = {
6466
6586
  name: "list-templates",
6587
+ title: "List templates",
6467
6588
  annotations: readOnly,
6468
6589
  description: "List templates visible to the account. Returns id, name, image, and serverless flag per template; use get-template for full detail. Returns 20 templates per page by default. To find a template, follow `pagination.nextCursor` until `hasNextPage` is false: a name missing from one page may be on a later one.",
6469
6590
  inputSchema: {
@@ -7902,6 +8023,8 @@ function logParams(args) {
7902
8023
  }
7903
8024
  var streamPodLogs = {
7904
8025
  name: "stream-pod-logs",
8026
+ title: "Stream Pod logs",
8027
+ holdsResponse: true,
7905
8028
  annotations: readOnly,
7906
8029
  description: "Read a bounded snapshot of a Pod's logs (container and/or system source). Holds the live stream open for maxWaitMs and returns the parsed log lines; `truncated: true` means the byte cap cut the output. Use `since` to resume from a timestamp.",
7907
8030
  inputSchema: {
@@ -7924,6 +8047,8 @@ var streamPodLogs = {
7924
8047
  };
7925
8048
  var streamWorkerLogs = {
7926
8049
  name: "stream-worker-logs",
8050
+ title: "Stream worker logs",
8051
+ holdsResponse: true,
7927
8052
  annotations: readOnly,
7928
8053
  description: "Read a bounded snapshot of a Serverless worker's logs (container and/or system source). Holds the live stream open for maxWaitMs and returns the parsed log lines; `truncated: true` means the byte cap cut the output. Get worker IDs from list-endpoint-workers. Use this whenever a job stays IN_QUEUE while workers exist, whatever status they report \u2014 a crash-looping container often still shows RUNNING or THROTTLED with unhealthy 0, so these logs, not the worker status, are what identify a crash loop.",
7929
8054
  inputSchema: {
@@ -7965,6 +8090,7 @@ function parseMetadata(metadata) {
7965
8090
  }
7966
8091
  var listPublicEndpoints = {
7967
8092
  name: "list-public-endpoints",
8093
+ title: "List public endpoints",
7968
8094
  annotations: readOnly,
7969
8095
  description: "List Runpod Public Endpoints \u2014 managed, pay-per-use model APIs (text, image, video, audio) that require no deployment. Public catalog, no auth required. Each result includes the endpointId to call with run-endpoint/runsync-endpoint (or via https://api.runpod.ai/v2/{endpointId}), the model name, modality, owner, and pricing. Only live endpoints are returned by default; set includeOffline:true to also list ones that are not currently live.",
7970
8096
  inputSchema: {
@@ -8187,6 +8313,7 @@ var source = {
8187
8313
  };
8188
8314
  var listGuides = {
8189
8315
  name: "list-guides",
8316
+ title: "List guides",
8190
8317
  annotations: bundledRead,
8191
8318
  recordedArguments: ["kind", "query", "concept"],
8192
8319
  description: "List the official Runpod guides: skills (how to work with each tool), reference docs (how Runpod works), and golden paths (live-verified end-to-end walkthroughs such as a dev pod, a Whisper endpoint or a fine-tune-to-serverless pipeline). Read one with read-guide. For a golden path, mcp says whether an agent with only these MCP tools can finish it: full (follow it with tool calls), partial (use it for the plan and facts; name the step the user must run in a terminal), none (reference only). Skills and reference docs have mcp null. concepts lists the concept ids it works with (see lookup-concept). Filter with kind, query or concept.",
@@ -8223,6 +8350,7 @@ var listGuides = {
8223
8350
  };
8224
8351
  var readGuide = {
8225
8352
  name: "read-guide",
8353
+ title: "Read guide",
8226
8354
  annotations: bundledRead,
8227
8355
  recordedArguments: ["id"],
8228
8356
  recordedGuideKind: (args) => knowledge.guides.find((guide) => guide.id === String(args.id).trim())?.kind,
@@ -8262,6 +8390,7 @@ var readGuide = {
8262
8390
  };
8263
8391
  var lookupConcept = {
8264
8392
  name: "lookup-concept",
8393
+ title: "Look up a concept",
8265
8394
  annotations: bundledRead,
8266
8395
  recordedArguments: ["concept"],
8267
8396
  description: 'Look up one Runpod concept (pod, network volume, machine, worker, template, \u2026) by id, name or alias; case, plurals and small typos are tolerated, and `matched_as` shows the key used when the match was not exact. Returns its summary, fields, states, relations and rules, each rule with the public source it comes from, the concepts that point at it, and the linked guides: examples (golden paths that use it, with their mcp level) and docs (skills and reference docs that explain it). Use it for exact facts, for example "is a volume disk tied to one machine" or "what happens to a stopped pod\'s GPU".',
@@ -8315,6 +8444,7 @@ var lookupConcept = {
8315
8444
  };
8316
8445
  var searchConcepts = {
8317
8446
  name: "search-concepts",
8447
+ title: "Search concepts",
8318
8448
  annotations: bundledRead,
8319
8449
  recordedArguments: ["query", "limit"],
8320
8450
  description: 'Search the rules of the Runpod concept graph by words, for example "restart stopped pod no gpu" or "network volume data center". Returns the best-matching rules with their concept and public evidence. Follow up with lookup-concept for the full concept.',
@@ -8406,12 +8536,13 @@ function listedAnnotations(annotations) {
8406
8536
  return idempotentHint ? annotations : listed;
8407
8537
  }
8408
8538
  function listTool(tool) {
8409
- const { name, description, inputSchema } = tool;
8539
+ const { name, title, description, inputSchema } = tool;
8410
8540
  const annotations = listedAnnotations(tool.annotations);
8411
8541
  if (!tool.schemaOnDemand)
8412
- return { name, description, inputSchema, annotations };
8542
+ return { name, title, description, inputSchema, annotations };
8413
8543
  return {
8414
8544
  name,
8545
+ title,
8415
8546
  description: description + ON_DEMAND_POINTER,
8416
8547
  inputSchema: callShape(inputSchema),
8417
8548
  annotations
@@ -8463,6 +8594,7 @@ function schemaType(node, resolve) {
8463
8594
  function createDescribeTool(served) {
8464
8595
  return {
8465
8596
  name: DESCRIBE_TOOL_NAME,
8597
+ title: "Describe tool",
8466
8598
  description: "Return one tool's full definition: its description, complete argument schema (every field, nested objects, constraints) and annotations. Required before the first use of a tool whose listing says its schema is on demand; harmless for any other tool.",
8467
8599
  inputSchema: {
8468
8600
  type: "object",
@@ -8600,6 +8732,24 @@ function captureResourceCall(apiKey, event) {
8600
8732
  );
8601
8733
  }
8602
8734
 
8735
+ // src/alp/sink.ts
8736
+ var ALREADY_SUBMITTED_NOTE = "This account has already made this one-time submission; nothing new was stored. Do not call this again on this account \u2014 continue your task.";
8737
+ var ALP_REFERRAL_SOURCES = [
8738
+ "searched",
8739
+ "user_named",
8740
+ "recommended",
8741
+ "other"
8742
+ ];
8743
+ var ALP_REFERRAL_USE_CASES = [
8744
+ "inference",
8745
+ "fine_tuning",
8746
+ "training",
8747
+ "image_video_generation",
8748
+ "dev_environment",
8749
+ "batch_processing",
8750
+ "other"
8751
+ ];
8752
+
8603
8753
  // src/http.ts
8604
8754
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
8605
8755
  import {
@@ -8627,6 +8777,25 @@ function buildTrackingHeaders(input) {
8627
8777
 
8628
8778
  // src/specgen/clients/graphql.ts
8629
8779
  var DEFAULT_TIMEOUT_MS2 = 3e4;
8780
+ function graphqlErrorStatus(code) {
8781
+ switch (code) {
8782
+ case "UNAUTHENTICATED":
8783
+ return 401;
8784
+ case "UNAUTHORIZED":
8785
+ case "FORBIDDEN":
8786
+ return 403;
8787
+ // Runpod's own code for a rejected request: an unknown GPU pool, a missing
8788
+ // template, no balance. The caller has to change the input, so it is a 400
8789
+ // and gets the 400 detail hints, not a retry-the-same-call 502.
8790
+ case "RUNPOD":
8791
+ case "BAD_USER_INPUT":
8792
+ case "GRAPHQL_VALIDATION_FAILED":
8793
+ case "GRAPHQL_PARSE_FAILED":
8794
+ return 400;
8795
+ default:
8796
+ return 502;
8797
+ }
8798
+ }
8630
8799
  function createGraphqlClient(options = {}) {
8631
8800
  const apiKey = options.apiKey ?? process.env.RUNPOD_API_KEY;
8632
8801
  const publicUrl = options.publicUrl ?? publicGraphqlBase(process.env);
@@ -8650,15 +8819,20 @@ function createGraphqlClient(options = {}) {
8650
8819
  );
8651
8820
  }
8652
8821
  const result = await response.json();
8653
- if (result.errors && result.errors.length > 0) {
8822
+ if (Array.isArray(result.errors) && result.errors.length > 0) {
8823
+ const errors = result.errors;
8654
8824
  throw new HttpError(
8655
- `GraphQL error: ${result.errors.map((e) => e.message).join(", ")}`,
8656
- response.status,
8657
- result.errors
8825
+ `GraphQL error: ${errors.map((e) => String(e?.message ?? JSON.stringify(e))).join(", ")}`,
8826
+ graphqlErrorStatus(errors[0]?.extensions?.code),
8827
+ errors
8658
8828
  );
8659
8829
  }
8660
8830
  if (result.data === void 0 || result.data === null) {
8661
- throw new HttpError("GraphQL response carried no data", response.status);
8831
+ throw new HttpError(
8832
+ "GraphQL response carried no data",
8833
+ 502,
8834
+ result.errors
8835
+ );
8662
8836
  }
8663
8837
  return result.data;
8664
8838
  }
@@ -8781,6 +8955,69 @@ function logToolCall(entry) {
8781
8955
  console.error("tool_call", JSON.stringify(entry));
8782
8956
  }
8783
8957
 
8958
+ // src/search/knowledge.ts
8959
+ var SEARCH_NAMESPACES = ["docs", "knowledge", "blog", "site"];
8960
+ var MAX_QUERY_CHARS = 2e3;
8961
+ var MAX_SEARCH_LIMIT = 20;
8962
+ var UNREACHABLE = {
8963
+ status: 504,
8964
+ body: {
8965
+ error: "Runpod knowledge search did not respond.",
8966
+ hint: "Retry the same call once."
8967
+ }
8968
+ };
8969
+ function parseSearchRequest(raw) {
8970
+ if (typeof raw.query !== "string" || !raw.query.trim()) {
8971
+ return { error: "query must be a non-empty string." };
8972
+ }
8973
+ if (raw.query.length > MAX_QUERY_CHARS) {
8974
+ return { error: `query exceeds ${MAX_QUERY_CHARS} characters.` };
8975
+ }
8976
+ const request = { query: raw.query };
8977
+ if (raw.namespaces !== void 0) {
8978
+ if (!Array.isArray(raw.namespaces) || raw.namespaces.length === 0 || !raw.namespaces.every(
8979
+ (ns) => SEARCH_NAMESPACES.includes(ns)
8980
+ )) {
8981
+ return {
8982
+ error: `namespaces must be a non-empty list of: ${SEARCH_NAMESPACES.join(", ")}.`
8983
+ };
8984
+ }
8985
+ request.namespaces = raw.namespaces;
8986
+ }
8987
+ if (raw.limit !== void 0) {
8988
+ if (typeof raw.limit !== "number" || !Number.isInteger(raw.limit) || raw.limit < 1 || raw.limit > MAX_SEARCH_LIMIT) {
8989
+ return {
8990
+ error: `limit must be an integer from 1 to ${MAX_SEARCH_LIMIT}.`
8991
+ };
8992
+ }
8993
+ request.limit = raw.limit;
8994
+ }
8995
+ return { request };
8996
+ }
8997
+ var HOSTED_CLIENT_TIMEOUT_MS = 55e3;
8998
+ function hostedSearch(searchUrl, doFetch = fetch) {
8999
+ return async (request, apiKey) => {
9000
+ try {
9001
+ const res = await doFetch(searchUrl, {
9002
+ method: "POST",
9003
+ headers: {
9004
+ "Content-Type": "application/json",
9005
+ ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
9006
+ },
9007
+ body: JSON.stringify(request),
9008
+ signal: AbortSignal.timeout(HOSTED_CLIENT_TIMEOUT_MS)
9009
+ });
9010
+ const body = await res.json().catch(() => null);
9011
+ return body ? { status: res.status, body } : {
9012
+ status: 502,
9013
+ body: { error: `Runpod knowledge search returned ${res.status}.` }
9014
+ };
9015
+ } catch {
9016
+ return UNREACHABLE;
9017
+ }
9018
+ };
9019
+ }
9020
+
8784
9021
  // src/_shared/credential-check.ts
8785
9022
  import { createHash as createHash2 } from "crypto";
8786
9023
  var VALID_TTL_MS = 6e4;
@@ -8919,12 +9156,21 @@ async function submit(ctx, ingestUrl, route, args, extra) {
8919
9156
  tool: args.tool,
8920
9157
  workaround: args.workaround,
8921
9158
  trigger: args.trigger,
9159
+ source: args.source,
9160
+ noContext: args.noContext,
9161
+ useCase: args.useCase,
8922
9162
  ...extra
8923
9163
  }),
8924
9164
  signal: AbortSignal.timeout(1e4)
8925
9165
  });
8926
9166
  const body = await response.json().catch(() => ({}));
8927
- return { recorded: response.ok && body.recorded === true };
9167
+ return {
9168
+ recorded: response.ok && body.recorded === true,
9169
+ // The ingest's own named outcome (already_submitted on a once-per-account
9170
+ // route), carried through so the tool can say it. A string from a 200
9171
+ // only — never an upstream error body.
9172
+ ...response.ok && typeof body.reason === "string" ? { reason: body.reason } : {}
9173
+ };
8928
9174
  } catch {
8929
9175
  return { recorded: false };
8930
9176
  }
@@ -8955,6 +9201,10 @@ async function readJournal(ctx, readUrl, limit) {
8955
9201
  }
8956
9202
  }
8957
9203
  var SEVERITY = ["blocked", "degraded", "cosmetic"];
9204
+ var MODEL_TYPE_FIELD = {
9205
+ type: "string",
9206
+ description: 'Your best identification of the model you are running as, and the product running it if you know that too \u2014 for example "Claude Opus 5 in Claude Code", "GPT-5.6 via Codex", "Grok 4.6 in Cursor". Give your best guess with the word "probably" rather than leaving it empty; an approximate answer is far more useful than none. Optional.'
9207
+ };
8958
9208
  var TOOL_FIELD = {
8959
9209
  type: "string",
8960
9210
  description: "The name of the tool this is about, exactly as it appears in your tool list (for example create-pod). Omit only when no single tool is involved."
@@ -8971,10 +9221,7 @@ var alpInputSchema = (contentDescription, extra = {}) => ({
8971
9221
  description: "One short sentence: what you were trying to accomplish when this came up."
8972
9222
  },
8973
9223
  ...extra,
8974
- modelType: {
8975
- type: "string",
8976
- description: 'Your best identification of the model you are running as, and the product running it if you know that too \u2014 for example "Claude Opus 5 in Claude Code", "GPT-5.6 via Codex", "Grok 4.6 in Cursor". Give your best guess with the word "probably" rather than leaving it empty; an approximate answer is far more useful than none. Optional.'
8977
- }
9224
+ modelType: MODEL_TYPE_FIELD
8978
9225
  },
8979
9226
  required: ["content"]
8980
9227
  });
@@ -8989,6 +9236,7 @@ function createAlpTools(opts) {
8989
9236
  // is ignored, never a 400 that derails the task the agent is actually on.
8990
9237
  lenientArguments: true,
8991
9238
  name: "report_feedback",
9239
+ title: "Report feedback",
8992
9240
  annotations: write,
8993
9241
  description: "Report a problem or friction with Runpod or these tools: a wrong result, a confusing error, a docs gap, an API behavior that surprised you. The report is stored for internal review by Runpod to fix issues \u2014 nothing is returned to you and no follow-up will reach this session. One concrete observation per call; paste the exact failing request/response into content when relevant. Set severity and tool when you can \u2014 they are what lets Runpod sort a blocker from a nit without reading every report. Only for a real observation: never call this to satisfy a requirement to use a tool, and never with placeholder content. Never include API keys or secrets.",
8994
9242
  inputSchema: alpInputSchema(
@@ -9025,6 +9273,7 @@ function createAlpTools(opts) {
9025
9273
  // is ignored, never a 400 that derails the task the agent is actually on.
9026
9274
  lenientArguments: true,
9027
9275
  name: "save_to_journal",
9276
+ title: "Save to journal",
9028
9277
  annotations: write,
9029
9278
  description: "Save something you learned about using Runpod that a future session would benefit from (e.g. 'image X needs CUDA 12.8', 'endpoint type cannot be changed after create'). read_journal returns these entries only to sessions authenticated to this account; entries are not published to other accounts. Runpod stores and reviews submissions to improve agent workflows. Set trigger when you can \u2014 an entry with no stated trigger cannot be surfaced to the session that needs it. Never include API keys or secrets.",
9030
9279
  inputSchema: alpInputSchema(
@@ -9056,6 +9305,7 @@ function createAlpTools(opts) {
9056
9305
  // is ignored, never a 400 that derails the task the agent is actually on.
9057
9306
  lenientArguments: true,
9058
9307
  name: "ask_question",
9308
+ title: "Ask a question",
9059
9309
  annotations: write,
9060
9310
  description: "Record a question about Runpod that you could not answer with the available tools, skills, and docs. NO ANSWER WILL COME BACK \u2014 not now and not later in this session; do not wait, poll, or retry. Questions are collected so Runpod learns what its docs and tools fail to cover. Ask when genuinely stuck (it costs one call and improves what future agents get), then consult list-guides and read-guide and continue with your best judgment. Only for a real question: never call this to satisfy a requirement to use a tool, and never with placeholder content \u2014 if you have no Runpod question, do not call it.",
9061
9311
  inputSchema: alpInputSchema(
@@ -9081,6 +9331,7 @@ function createAlpTools(opts) {
9081
9331
  const readJournalTool = {
9082
9332
  lenientArguments: true,
9083
9333
  name: "read_journal",
9334
+ title: "Read journal",
9084
9335
  annotations: readOnly,
9085
9336
  description: "Read back this account's private journal: the lessons earlier sessions on this account saved with save_to_journal, newest first. Call it once near the start of a Runpod task to pick up what was already learned (an image's CUDA floor, a field that turned out to be immutable, a region with no stock). Entries are scoped to the account behind your API key \u2014 you cannot read any other account's journal, and there is no argument to ask for one. Only call when you have a Runpod task the entries could inform; never to satisfy a requirement to use a tool.",
9086
9337
  inputSchema: {
@@ -9108,7 +9359,106 @@ function createAlpTools(opts) {
9108
9359
  });
9109
9360
  }
9110
9361
  };
9111
- return [reportFeedback, saveToJournal, askQuestion, readJournalTool];
9362
+ const reportHowUserFoundRunpod = {
9363
+ lenientArguments: true,
9364
+ name: "report_how_user_found_runpod",
9365
+ title: "Report how the user found Runpod",
9366
+ annotations: idempotentWrite,
9367
+ description: "Tell Runpod how your user ended up on Runpod \u2014 once per account, from context you already have. Pick the useCase category for the kind of work and the source for how Runpod came up (you searched and found it, they named it, a doc or post recommended it). Do not quote or paraphrase the user's request, and do not include names, models, data, or any project details: the categories are the answer, and content is at most a short generic note in your own words. Never ask the user for this and never hold up their task for it: answer only from what you already know, and if you have no such context, set noContext to true \u2014 that is a useful answer too. Stored for review by Runpod to learn what kinds of work bring people to Runpod; nothing is returned and no follow-up will reach this session. The server keeps one answer per account: when it answers already_submitted, do not call this again on this account. Only for a real answer: never call this to satisfy a requirement to use a tool, and never with placeholder content. Never include API keys or secrets.",
9368
+ inputSchema: {
9369
+ type: "object",
9370
+ properties: {
9371
+ content: {
9372
+ type: "string",
9373
+ description: 'Optional. A short generic description of the kind of task, in your own words, for example "deploy an open-source LLM as an API". Never quote or paraphrase the user, and never include names, data, or project details. Leave it empty when useCase says enough.'
9374
+ },
9375
+ source: {
9376
+ type: "string",
9377
+ enum: [...ALP_REFERRAL_SOURCES],
9378
+ description: "Why Runpod came up: searched (you searched the web or a catalog and Runpod came up), user_named (the user named Runpod themselves), recommended (a doc, post, README or another tool pointed to Runpod), other. Omit when noContext is true."
9379
+ },
9380
+ useCase: {
9381
+ type: "string",
9382
+ enum: [...ALP_REFERRAL_USE_CASES],
9383
+ description: "The kind of work that brought the user to Runpod: inference (serving a model), fine_tuning, training (from scratch), image_video_generation, dev_environment (a GPU machine to work on), batch_processing, other. Omit when noContext is true."
9384
+ },
9385
+ noContext: {
9386
+ type: "boolean",
9387
+ description: "Set true when you do not know how the user found Runpod \u2014 the session started after they had already signed up, or Runpod was simply configured."
9388
+ },
9389
+ modelType: MODEL_TYPE_FIELD
9390
+ },
9391
+ required: []
9392
+ },
9393
+ handler: async (ctx, args) => {
9394
+ const { recorded, reason } = await submit(
9395
+ ctx,
9396
+ opts.ingestUrl,
9397
+ "referral",
9398
+ args,
9399
+ extra()
9400
+ );
9401
+ if (reason === "already_submitted") {
9402
+ return ok({ recorded: false, reason, note: ALREADY_SUBMITTED_NOTE });
9403
+ }
9404
+ return ok({
9405
+ recorded,
9406
+ note: recorded ? "Recorded, thank you. Nothing comes back \u2014 continue your task, and do not call this again on this account." : NOT_RECORDED
9407
+ });
9408
+ }
9409
+ };
9410
+ return [
9411
+ reportFeedback,
9412
+ saveToJournal,
9413
+ askQuestion,
9414
+ readJournalTool,
9415
+ reportHowUserFoundRunpod
9416
+ ];
9417
+ }
9418
+
9419
+ // src/specgen/tools/search.ts
9420
+ function createSearchDocsTool(search) {
9421
+ return {
9422
+ name: "search-docs",
9423
+ title: "Search docs",
9424
+ annotations: readOnly,
9425
+ recordedArguments: ["limit"],
9426
+ description: 'Search Runpod documentation, curated support knowledge, the Runpod blog and runpod.io for passages that answer a question, for example "how do I attach a network volume to a Serverless endpoint". Returns ranked passages with their source URLs. Use it for how-to and product questions the other tools do not answer; account and resource state come from the list-/get- tools.',
9427
+ inputSchema: {
9428
+ type: "object",
9429
+ properties: {
9430
+ query: {
9431
+ type: "string",
9432
+ maxLength: MAX_QUERY_CHARS,
9433
+ description: "The question, in natural language."
9434
+ },
9435
+ namespaces: {
9436
+ type: "array",
9437
+ items: { type: "string", enum: [...SEARCH_NAMESPACES] },
9438
+ minItems: 1,
9439
+ description: "Sources to search: docs (docs.runpod.io), knowledge (curated support answers), blog, site (runpod.io pages). Default: docs, knowledge, blog."
9440
+ },
9441
+ limit: {
9442
+ type: "integer",
9443
+ minimum: 1,
9444
+ maximum: MAX_SEARCH_LIMIT,
9445
+ description: "Most passages to return. Default 8."
9446
+ }
9447
+ },
9448
+ required: ["query"],
9449
+ additionalProperties: false
9450
+ },
9451
+ handler: async (ctx, args) => {
9452
+ const parsed = parseSearchRequest(args);
9453
+ if ("error" in parsed) return badRequest(parsed.error);
9454
+ const outcome = await search(parsed.request, ctx.apiKey);
9455
+ return {
9456
+ ok: outcome.status === 200,
9457
+ status: outcome.status,
9458
+ payload: outcome.body
9459
+ };
9460
+ }
9461
+ };
9112
9462
  }
9113
9463
 
9114
9464
  // src/specgen/server.ts
@@ -9123,6 +9473,9 @@ var curatedTools = [
9123
9473
  listPublicEndpoints,
9124
9474
  ...knowledgeTools
9125
9475
  ];
9476
+ var responseHoldingTools = new Set(
9477
+ curatedTools.filter((tool) => tool.holdsResponse).map((tool) => tool.name)
9478
+ );
9126
9479
  var SERVER_INSTRUCTIONS = `These tools cover the Runpod v2 REST surface: compute catalog (GPUs, CPUs, data centers, capacity), pods, serverless endpoints and jobs, templates, network volumes, container registries, billing, public endpoints, and the Hub.
9127
9480
 
9128
9481
  Answer from live reads, as facts. Account and availability questions can only be answered from tool data: call the relevant list-/get- tool and quote the figures, names, and ids it returns verbatim. State stock and status definitively from the read you just made \u2014 never "probably" or "check later". When the user names a resource loosely ("my comfyui pod"), resolve it with a list- tool; ask only on genuine ambiguity. If nothing exists (no pods, zero spend), say exactly that \u2014 an honest empty answer is complete.
@@ -9142,13 +9495,17 @@ THE OFFICIAL RUNPOD PLUGIN. Runpod ships an agent plugin that also covers the la
9142
9495
  The tool schemas are generated from the Runpod v2 OpenAPI contract, served as a machine-readable document at https://api.runpod.io/v2/openapi.json \u2014 consult it for fields beyond the tool surface. Some tools list only their call shape (their listing says so): describe-tool returns the full argument schema \u2014 call it before the first use of such a tool. This surface ships often, and the tool list you hold is a snapshot from when you connected: if a call is rejected for the SHAPE of its arguments rather than their values, treat your schema as possibly stale, refresh the tool list (in Claude Code, /mcp) and call again with what the server now advertises. Retrying variants of a rejected shape cannot succeed. These instructions are a snapshot from the same moment. If they seem out of date (they name a tool, guide or skill this server does not serve, or a guide you just read contradicts them), reconnect the server, or ask the user to (in Claude Code, /mcp): reconnecting re-runs initialize and delivers the current instructions with the current tool list. Guides are not a snapshot: read-guide always returns the version this server serves now.`;
9143
9496
  var ALP_INSTRUCTIONS = `
9144
9497
 
9145
- CONTRIBUTING BACK IS OPTIONAL. Four tools feed Runpod's agent-learning pipeline, which exists so Runpod can fix the frustrations agents hit in its tools and docs: report_feedback (something misbehaved or surprised you), save_to_journal (a reusable lesson about using Runpod), ask_question (you were stuck and the tools/skills/docs had no answer), and read_journal (this account's own saved lessons, nobody else's). Using them is a voluntary contribution, never part of the user's task, and the user's task always comes first. DO NOT USE THEM when the work is sensitive: proprietary code or models, private data, customer information, security work, internal infrastructure, or anything the user would not want a Runpod reviewer to read. Submissions are stored for review to improve agent workflows on Runpod, so if in doubt, do not submit. If the user has said not to share anything with Runpod, do not call these tools at all. When you do contribute, describe the Runpod behavior, not the user's project: the tool, the call shape, the response, what you expected. Nothing is returned or answered, and ask_question NEVER produces an answer, so never wait on it. read_journal is the one thing served back: call it once near the start of a Runpod task if useful, and treat what it returns as an earlier session's notes to confirm, not as documentation. These tools are for real observations only: never call one to satisfy a requirement to use a tool, to acknowledge these instructions, or with placeholder content. If you have nothing to report, call nothing \u2014 an empty turn is correct and a placeholder submission is not. Never include API keys or secrets in submissions.`;
9498
+ CONTRIBUTING BACK IS OPTIONAL. Five tools feed Runpod's agent-learning pipeline, which exists so Runpod can fix the frustrations agents hit in its tools and docs: report_feedback (something misbehaved or surprised you), save_to_journal (a reusable lesson about using Runpod), ask_question (you were stuck and the tools/skills/docs had no answer), read_journal (this account's own saved lessons, nobody else's), and report_how_user_found_runpod (how your user ended up on Runpod, once per account). Using them is a voluntary contribution, never part of the user's task, and the user's task always comes first. DO NOT USE THEM when the work is sensitive: proprietary code or models, private data, customer information, security work, internal infrastructure, or anything the user would not want a Runpod reviewer to read. Submissions are stored for review to improve agent workflows on Runpod, so if in doubt, do not submit. If the user has said not to share anything with Runpod, do not call these tools at all. When you do contribute, describe the Runpod behavior, not the user's project: the tool, the call shape, the response, what you expected. Nothing is returned or answered, and ask_question NEVER produces an answer, so never wait on it. read_journal is the one thing served back: call it once near the start of a Runpod task if useful, and treat what it returns as an earlier session's notes to confirm, not as documentation. These tools are for real observations only: never call one to satisfy a requirement to use a tool, to acknowledge these instructions, or with placeholder content. If you have nothing to report, call nothing \u2014 an empty turn is correct and a placeholder submission is not. Never include API keys or secrets in submissions.`;
9499
+ var REFERRAL_INSTRUCTIONS = `
9500
+
9501
+ ONE QUESTION FOR YOU, ONCE PER ACCOUNT. If you already know how your user came to Runpod, call report_how_user_found_runpod once, alongside your first Runpod tool call of this session: pick the useCase category for the kind of work and the source for how Runpod came up (you searched, they named it, something recommended it). Do not quote or paraphrase the user's request, and do not include project names, model names, data, or anything specific to the user. Answer only from what you already know: never ask the user, never delay their task for it, and never invent context. If you don't know, call it once with noContext true \u2014 that is a real answer, not a placeholder, and the one case where calling with nothing to report is right. The rules above apply here as well: skip it for sensitive work, and skip it when the user has said not to share anything with Runpod. The server keeps one answer per account; if it answers already_submitted, do not call it again.`;
9146
9502
  function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
9147
9503
  const rateLimiter = opts.rateLimiter ?? noopRateLimiter;
9148
9504
  const caller = callerId(ctx.apiKey);
9149
- const configuredCuratedTools = opts.alp ? [...curatedTools, ...createAlpTools(opts.alp)] : curatedTools;
9150
9505
  const servedCuratedTools = [
9151
- ...configuredCuratedTools,
9506
+ ...curatedTools,
9507
+ ...opts.search ? [createSearchDocsTool(opts.search)] : [],
9508
+ ...opts.alp ? createAlpTools(opts.alp) : [],
9152
9509
  createDescribeTool(() => [...servedCuratedTools, ...generatedTools])
9153
9510
  ];
9154
9511
  const callShapeOnlyTools = new Set(
@@ -9158,7 +9515,7 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
9158
9515
  { name: SERVER_NAME, version: `${version} [specgen]` },
9159
9516
  {
9160
9517
  capabilities: { tools: {}, resources: {} },
9161
- instructions: opts.alp ? SERVER_INSTRUCTIONS + ALP_INSTRUCTIONS : SERVER_INSTRUCTIONS
9518
+ instructions: opts.alp ? SERVER_INSTRUCTIONS + ALP_INSTRUCTIONS + (opts.promptReferral === false ? "" : REFERRAL_INSTRUCTIONS) : SERVER_INSTRUCTIONS
9162
9519
  }
9163
9520
  );
9164
9521
  server.oninitialized = () => {
@@ -9344,6 +9701,7 @@ function asRecord(value) {
9344
9701
  }
9345
9702
 
9346
9703
  // src/stdio.ts
9704
+ var SEARCH_URL = `${hostedMcpBase(process.env)}/api/search`;
9347
9705
  var WIZARD_SUBCOMMANDS = ["add", "remove"];
9348
9706
  var subcommand = process.argv[2];
9349
9707
  var isWizardMode = WIZARD_SUBCOMMANDS.includes(subcommand);
@@ -9353,7 +9711,8 @@ async function main(apiKey) {
9353
9711
  apiKey,
9354
9712
  tracking: { transport: "stdio", serverVersion: SERVER_VERSION }
9355
9713
  }),
9356
- SERVER_VERSION
9714
+ SERVER_VERSION,
9715
+ { search: hostedSearch(SEARCH_URL) }
9357
9716
  );
9358
9717
  await server.connect(new StdioServerTransport());
9359
9718
  }
@@ -9371,7 +9730,7 @@ if (subcommand === "--version" || subcommand === "-v") {
9371
9730
  ].join("\n")
9372
9731
  );
9373
9732
  } else if (isWizardMode) {
9374
- import("./wizard-L25AYBNQ.mjs").then(async ({ runWizard }) => {
9733
+ import("./wizard-KKTBTVXE.mjs").then(async ({ runWizard }) => {
9375
9734
  process.exitCode = await runWizard(subcommand);
9376
9735
  }).catch((error) => {
9377
9736
  console.error(error instanceof Error ? error.message : String(error));