@runpod/mcp-server 4.4.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.cjs CHANGED
@@ -1183,7 +1183,7 @@ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
1183
1183
 
1184
1184
  // src/server.ts
1185
1185
  var SERVER_NAME = "Runpod API Server";
1186
- var SERVER_VERSION = true ? "4.4.0" : "dev";
1186
+ var SERVER_VERSION = true ? "4.5.0" : "dev";
1187
1187
 
1188
1188
  // src/specgen/server.ts
1189
1189
  var import_server3 = require("@modelcontextprotocol/sdk/server/index.js");
@@ -1510,6 +1510,7 @@ function coerceScalars(value, schema, defs, depth = 0) {
1510
1510
  var generatedTools = [
1511
1511
  {
1512
1512
  name: "create-cluster",
1513
+ title: "Create cluster",
1513
1514
  operationId: "createCluster",
1514
1515
  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.",
1515
1516
  method: "POST",
@@ -1747,6 +1748,7 @@ var generatedTools = [
1747
1748
  },
1748
1749
  {
1749
1750
  name: "create-delegation",
1751
+ title: "Create ECR delegation",
1750
1752
  operationId: "createDelegation",
1751
1753
  description: "Register an ECR delegation",
1752
1754
  method: "POST",
@@ -1792,6 +1794,7 @@ var generatedTools = [
1792
1794
  },
1793
1795
  {
1794
1796
  name: "create-endpoint",
1797
+ title: "Create endpoint",
1795
1798
  operationId: "createEndpoint",
1796
1799
  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.",
1797
1800
  method: "POST",
@@ -2237,6 +2240,7 @@ var generatedTools = [
2237
2240
  },
2238
2241
  {
2239
2242
  name: "create-global-volume",
2243
+ title: "Create global volume",
2240
2244
  operationId: "createGlobalVolume",
2241
2245
  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`.",
2242
2246
  method: "POST",
@@ -2278,6 +2282,7 @@ var generatedTools = [
2278
2282
  },
2279
2283
  {
2280
2284
  name: "create-network-volume",
2285
+ title: "Create network volume",
2281
2286
  operationId: "createNetworkVolume",
2282
2287
  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`.",
2283
2288
  method: "POST",
@@ -2343,6 +2348,7 @@ var generatedTools = [
2343
2348
  },
2344
2349
  {
2345
2350
  name: "create-pod",
2351
+ title: "Create Pod",
2346
2352
  operationId: "createPod",
2347
2353
  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.',
2348
2354
  method: "POST",
@@ -2710,6 +2716,7 @@ var generatedTools = [
2710
2716
  },
2711
2717
  {
2712
2718
  name: "create-registry",
2719
+ title: "Create registry credential",
2713
2720
  operationId: "createRegistry",
2714
2721
  description: "Create a container registry credential. Stores credentials for a private container registry. Credentials are write-only.",
2715
2722
  method: "POST",
@@ -2761,6 +2768,7 @@ var generatedTools = [
2761
2768
  },
2762
2769
  {
2763
2770
  name: "create-secret",
2771
+ title: "Create secret",
2764
2772
  operationId: "createSecret",
2765
2773
  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.",
2766
2774
  method: "POST",
@@ -2815,6 +2823,7 @@ var generatedTools = [
2815
2823
  },
2816
2824
  {
2817
2825
  name: "create-template",
2826
+ title: "Create template",
2818
2827
  operationId: "createTemplate",
2819
2828
  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.",
2820
2829
  method: "POST",
@@ -3016,6 +3025,7 @@ var generatedTools = [
3016
3025
  },
3017
3026
  {
3018
3027
  name: "delete-cluster",
3028
+ title: "Delete cluster",
3019
3029
  operationId: "deleteCluster",
3020
3030
  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.",
3021
3031
  method: "DELETE",
@@ -3046,6 +3056,7 @@ var generatedTools = [
3046
3056
  },
3047
3057
  {
3048
3058
  name: "delete-endpoint",
3059
+ title: "Delete endpoint",
3049
3060
  operationId: "deleteEndpoint",
3050
3061
  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.",
3051
3062
  method: "DELETE",
@@ -3076,6 +3087,7 @@ var generatedTools = [
3076
3087
  },
3077
3088
  {
3078
3089
  name: "delete-global-volume",
3090
+ title: "Delete global volume",
3079
3091
  operationId: "deleteGlobalVolume",
3080
3092
  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.",
3081
3093
  method: "DELETE",
@@ -3106,6 +3118,7 @@ var generatedTools = [
3106
3118
  },
3107
3119
  {
3108
3120
  name: "delete-network-volume",
3121
+ title: "Delete network volume",
3109
3122
  operationId: "deleteNetworkVolume",
3110
3123
  description: "Delete a network volume. Permanently deletes a network volume and releases its storage.",
3111
3124
  method: "DELETE",
@@ -3136,6 +3149,7 @@ var generatedTools = [
3136
3149
  },
3137
3150
  {
3138
3151
  name: "delete-pod",
3152
+ title: "Delete Pod",
3139
3153
  operationId: "deletePod",
3140
3154
  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.",
3141
3155
  method: "DELETE",
@@ -3166,6 +3180,7 @@ var generatedTools = [
3166
3180
  },
3167
3181
  {
3168
3182
  name: "delete-registry",
3183
+ title: "Delete registry credential",
3169
3184
  operationId: "deleteRegistry",
3170
3185
  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.",
3171
3186
  method: "DELETE",
@@ -3195,6 +3210,7 @@ var generatedTools = [
3195
3210
  },
3196
3211
  {
3197
3212
  name: "delete-secret",
3213
+ title: "Delete secret",
3198
3214
  operationId: "deleteSecret",
3199
3215
  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.",
3200
3216
  method: "DELETE",
@@ -3225,6 +3241,7 @@ var generatedTools = [
3225
3241
  },
3226
3242
  {
3227
3243
  name: "delete-template",
3244
+ title: "Delete template",
3228
3245
  operationId: "deleteTemplate",
3229
3246
  description: "Delete a template permanently. Verify with get-template first; a template referenced by a live endpoint should not be deleted.",
3230
3247
  method: "DELETE",
@@ -3254,6 +3271,7 @@ var generatedTools = [
3254
3271
  },
3255
3272
  {
3256
3273
  name: "get-cluster",
3274
+ title: "Get cluster",
3257
3275
  operationId: "getCluster",
3258
3276
  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.",
3259
3277
  method: "GET",
@@ -3284,6 +3302,7 @@ var generatedTools = [
3284
3302
  },
3285
3303
  {
3286
3304
  name: "get-cpu-type",
3305
+ title: "Get CPU type",
3287
3306
  operationId: "getCpuType",
3288
3307
  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.",
3289
3308
  method: "GET",
@@ -3360,6 +3379,7 @@ var generatedTools = [
3360
3379
  },
3361
3380
  {
3362
3381
  name: "get-data-center",
3382
+ title: "Get data center",
3363
3383
  operationId: "getDataCenter",
3364
3384
  description: "Get a data center. Returns a single data center. Availability is included only when requested with include=GPU_AVAILABILITY or include=CPU_AVAILABILITY.",
3365
3385
  method: "GET",
@@ -3409,6 +3429,7 @@ var generatedTools = [
3409
3429
  },
3410
3430
  {
3411
3431
  name: "get-endpoint",
3432
+ title: "Get endpoint",
3412
3433
  operationId: "getEndpoint",
3413
3434
  description: "Get a serverless endpoint. Returns a single serverless endpoint by ID.",
3414
3435
  method: "GET",
@@ -3439,6 +3460,7 @@ var generatedTools = [
3439
3460
  },
3440
3461
  {
3441
3462
  name: "get-endpoint-build",
3463
+ title: "Get endpoint build",
3442
3464
  operationId: "getEndpointBuild",
3443
3465
  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.",
3444
3466
  method: "GET",
@@ -3477,6 +3499,7 @@ var generatedTools = [
3477
3499
  },
3478
3500
  {
3479
3501
  name: "get-global-volume",
3502
+ title: "Get global volume",
3480
3503
  operationId: "getGlobalVolume",
3481
3504
  description: "Get a global volume. Returns a single global volume by ID.",
3482
3505
  method: "GET",
@@ -3507,6 +3530,7 @@ var generatedTools = [
3507
3530
  },
3508
3531
  {
3509
3532
  name: "get-gpu-type",
3533
+ title: "Get GPU type",
3510
3534
  operationId: "getGpuType",
3511
3535
  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.",
3512
3536
  method: "GET",
@@ -3631,6 +3655,7 @@ var generatedTools = [
3631
3655
  },
3632
3656
  {
3633
3657
  name: "get-network-volume",
3658
+ title: "Get network volume",
3634
3659
  operationId: "getNetworkVolume",
3635
3660
  description: "Get a network volume. Returns a single network volume by ID.",
3636
3661
  method: "GET",
@@ -3661,6 +3686,7 @@ var generatedTools = [
3661
3686
  },
3662
3687
  {
3663
3688
  name: "get-organization",
3689
+ title: "Get organization",
3664
3690
  operationId: "getOrganization",
3665
3691
  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.",
3666
3692
  method: "GET",
@@ -3680,6 +3706,7 @@ var generatedTools = [
3680
3706
  },
3681
3707
  {
3682
3708
  name: "get-pod",
3709
+ title: "Get Pod",
3683
3710
  operationId: "getPod",
3684
3711
  description: "Get a pod. Returns a single pod by ID.",
3685
3712
  method: "GET",
@@ -3710,6 +3737,7 @@ var generatedTools = [
3710
3737
  },
3711
3738
  {
3712
3739
  name: "get-registry",
3740
+ title: "Get registry credential",
3713
3741
  operationId: "getRegistry",
3714
3742
  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`.",
3715
3743
  method: "GET",
@@ -3739,6 +3767,7 @@ var generatedTools = [
3739
3767
  },
3740
3768
  {
3741
3769
  name: "get-secret",
3770
+ title: "Get secret",
3742
3771
  operationId: "getSecret",
3743
3772
  description: "Get a secret. Returns a single secret's metadata by ID. The value is write-only and never returned.",
3744
3773
  method: "GET",
@@ -3769,6 +3798,7 @@ var generatedTools = [
3769
3798
  },
3770
3799
  {
3771
3800
  name: "get-ssh-keys",
3801
+ title: "Get SSH keys",
3772
3802
  operationId: "getSshKeys",
3773
3803
  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.",
3774
3804
  method: "GET",
@@ -3788,6 +3818,7 @@ var generatedTools = [
3788
3818
  },
3789
3819
  {
3790
3820
  name: "get-template",
3821
+ title: "Get template",
3791
3822
  operationId: "getTemplate",
3792
3823
  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.",
3793
3824
  method: "GET",
@@ -3817,6 +3848,7 @@ var generatedTools = [
3817
3848
  },
3818
3849
  {
3819
3850
  name: "list-billing",
3851
+ title: "List billing",
3820
3852
  operationId: "listBilling",
3821
3853
  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.",
3822
3854
  method: "GET",
@@ -3889,6 +3921,7 @@ var generatedTools = [
3889
3921
  },
3890
3922
  {
3891
3923
  name: "list-cluster-billing",
3924
+ title: "List cluster billing",
3892
3925
  operationId: "listClusterBilling",
3893
3926
  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.",
3894
3927
  method: "GET",
@@ -3969,6 +4002,7 @@ var generatedTools = [
3969
4002
  },
3970
4003
  {
3971
4004
  name: "list-cluster-pods",
4005
+ title: "List cluster Pods",
3972
4006
  operationId: "listClusterPods",
3973
4007
  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.",
3974
4008
  method: "GET",
@@ -3999,6 +4033,7 @@ var generatedTools = [
3999
4033
  },
4000
4034
  {
4001
4035
  name: "list-clusters",
4036
+ title: "List clusters",
4002
4037
  operationId: "listClusters",
4003
4038
  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.",
4004
4039
  method: "GET",
@@ -4039,6 +4074,7 @@ var generatedTools = [
4039
4074
  },
4040
4075
  {
4041
4076
  name: "list-cpu-types",
4077
+ title: "List CPU types",
4042
4078
  operationId: "listCpuTypes",
4043
4079
  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.",
4044
4080
  method: "GET",
@@ -4107,6 +4143,7 @@ var generatedTools = [
4107
4143
  },
4108
4144
  {
4109
4145
  name: "list-data-centers",
4146
+ title: "List data centers",
4110
4147
  operationId: "listDataCenters",
4111
4148
  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.",
4112
4149
  method: "GET",
@@ -4234,6 +4271,7 @@ var generatedTools = [
4234
4271
  },
4235
4272
  {
4236
4273
  name: "list-delegations",
4274
+ title: "List ECR delegations",
4237
4275
  operationId: "listDelegations",
4238
4276
  description: "List all ECR delegations",
4239
4277
  method: "GET",
@@ -4253,6 +4291,7 @@ var generatedTools = [
4253
4291
  },
4254
4292
  {
4255
4293
  name: "list-endpoint-billing",
4294
+ title: "List endpoint billing",
4256
4295
  operationId: "listEndpointBilling",
4257
4296
  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.",
4258
4297
  method: "GET",
@@ -4325,6 +4364,7 @@ var generatedTools = [
4325
4364
  },
4326
4365
  {
4327
4366
  name: "list-endpoint-builds",
4367
+ title: "List endpoint builds",
4328
4368
  operationId: "listEndpointBuilds",
4329
4369
  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`.",
4330
4370
  method: "GET",
@@ -4374,6 +4414,7 @@ var generatedTools = [
4374
4414
  },
4375
4415
  {
4376
4416
  name: "list-endpoint-releases",
4417
+ title: "List endpoint releases",
4377
4418
  operationId: "listEndpointReleases",
4378
4419
  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.",
4379
4420
  method: "GET",
@@ -4423,6 +4464,7 @@ var generatedTools = [
4423
4464
  },
4424
4465
  {
4425
4466
  name: "list-endpoint-workers",
4467
+ title: "List endpoint workers",
4426
4468
  operationId: "listEndpointWorkers",
4427
4469
  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.",
4428
4470
  method: "GET",
@@ -4453,6 +4495,7 @@ var generatedTools = [
4453
4495
  },
4454
4496
  {
4455
4497
  name: "list-global-volumes",
4498
+ title: "List global volumes",
4456
4499
  operationId: "listGlobalVolumes",
4457
4500
  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.",
4458
4501
  method: "GET",
@@ -4493,6 +4536,7 @@ var generatedTools = [
4493
4536
  },
4494
4537
  {
4495
4538
  name: "list-gpu-types",
4539
+ title: "List GPU types",
4496
4540
  operationId: "listGpuTypes",
4497
4541
  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.',
4498
4542
  method: "GET",
@@ -4609,6 +4653,7 @@ var generatedTools = [
4609
4653
  },
4610
4654
  {
4611
4655
  name: "list-network-volume-billing",
4656
+ title: "List network volume billing",
4612
4657
  operationId: "listNetworkVolumeBilling",
4613
4658
  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.",
4614
4659
  method: "GET",
@@ -4689,6 +4734,7 @@ var generatedTools = [
4689
4734
  },
4690
4735
  {
4691
4736
  name: "list-network-volumes",
4737
+ title: "List network volumes",
4692
4738
  operationId: "listNetworkVolumes",
4693
4739
  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.",
4694
4740
  method: "GET",
@@ -4729,6 +4775,7 @@ var generatedTools = [
4729
4775
  },
4730
4776
  {
4731
4777
  name: "list-pod-billing",
4778
+ title: "List Pod billing",
4732
4779
  operationId: "listPodBilling",
4733
4780
  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.",
4734
4781
  method: "GET",
@@ -4809,6 +4856,7 @@ var generatedTools = [
4809
4856
  },
4810
4857
  {
4811
4858
  name: "list-pods",
4859
+ title: "List Pods",
4812
4860
  operationId: "listPods",
4813
4861
  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.",
4814
4862
  method: "GET",
@@ -4858,6 +4906,7 @@ var generatedTools = [
4858
4906
  },
4859
4907
  {
4860
4908
  name: "list-public-templates",
4909
+ title: "List public templates",
4861
4910
  operationId: "listPublicTemplates",
4862
4911
  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`).",
4863
4912
  method: "GET",
@@ -4889,6 +4938,7 @@ var generatedTools = [
4889
4938
  },
4890
4939
  {
4891
4940
  name: "list-registries",
4941
+ title: "List registry credentials",
4892
4942
  operationId: "listRegistries",
4893
4943
  description: "List container registries. Returns all container registry credentials owned by the authenticated user.",
4894
4944
  method: "GET",
@@ -4908,6 +4958,7 @@ var generatedTools = [
4908
4958
  },
4909
4959
  {
4910
4960
  name: "list-secrets",
4961
+ title: "List secrets",
4911
4962
  operationId: "listSecrets",
4912
4963
  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.",
4913
4964
  method: "GET",
@@ -4937,6 +4988,7 @@ var generatedTools = [
4937
4988
  },
4938
4989
  {
4939
4990
  name: "list-serverless-billing",
4991
+ title: "List Serverless billing",
4940
4992
  operationId: "listServerlessBilling",
4941
4993
  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.",
4942
4994
  method: "GET",
@@ -5017,6 +5069,7 @@ var generatedTools = [
5017
5069
  },
5018
5070
  {
5019
5071
  name: "pod-action",
5072
+ title: "Start, stop, restart, or terminate a Pod",
5020
5073
  operationId: "podAction",
5021
5074
  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`.",
5022
5075
  method: "POST",
@@ -5066,6 +5119,7 @@ var generatedTools = [
5066
5119
  },
5067
5120
  {
5068
5121
  name: "revoke-delegation",
5122
+ title: "Revoke ECR delegation",
5069
5123
  operationId: "revokeDelegation",
5070
5124
  description: "Revoke an ECR delegation",
5071
5125
  method: "DELETE",
@@ -5095,6 +5149,7 @@ var generatedTools = [
5095
5149
  },
5096
5150
  {
5097
5151
  name: "update-cluster",
5152
+ title: "Update cluster",
5098
5153
  operationId: "updateCluster",
5099
5154
  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.",
5100
5155
  method: "PATCH",
@@ -5143,6 +5198,7 @@ var generatedTools = [
5143
5198
  },
5144
5199
  {
5145
5200
  name: "update-endpoint",
5201
+ title: "Update endpoint",
5146
5202
  operationId: "updateEndpoint",
5147
5203
  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.",
5148
5204
  method: "PATCH",
@@ -5533,6 +5589,7 @@ var generatedTools = [
5533
5589
  },
5534
5590
  {
5535
5591
  name: "update-global-volume",
5592
+ title: "Update global volume",
5536
5593
  operationId: "updateGlobalVolume",
5537
5594
  description: "Update a global volume. Updates mutable fields on a global volume. Only provided fields are changed.",
5538
5595
  method: "PATCH",
@@ -5584,6 +5641,7 @@ var generatedTools = [
5584
5641
  },
5585
5642
  {
5586
5643
  name: "update-network-volume",
5644
+ title: "Update network volume",
5587
5645
  operationId: "updateNetworkVolume",
5588
5646
  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.",
5589
5647
  method: "PATCH",
@@ -5640,6 +5698,7 @@ var generatedTools = [
5640
5698
  },
5641
5699
  {
5642
5700
  name: "update-pod",
5701
+ title: "Update Pod",
5643
5702
  operationId: "updatePod",
5644
5703
  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.",
5645
5704
  method: "PATCH",
@@ -5881,6 +5940,7 @@ var generatedTools = [
5881
5940
  },
5882
5941
  {
5883
5942
  name: "update-secret",
5943
+ title: "Update secret",
5884
5944
  operationId: "updateSecret",
5885
5945
  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.",
5886
5946
  method: "PATCH",
@@ -5936,6 +5996,7 @@ var generatedTools = [
5936
5996
  },
5937
5997
  {
5938
5998
  name: "update-ssh-keys",
5999
+ title: "Update SSH keys",
5939
6000
  operationId: "updateSshKeys",
5940
6001
  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.",
5941
6002
  method: "PUT",
@@ -5982,6 +6043,7 @@ var generatedTools = [
5982
6043
  },
5983
6044
  {
5984
6045
  name: "update-template",
6046
+ title: "Update template",
5985
6047
  operationId: "updateTemplate",
5986
6048
  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.",
5987
6049
  method: "PATCH",
@@ -6280,6 +6342,43 @@ function isTimeout(error) {
6280
6342
  const cause = error.cause;
6281
6343
  return cause instanceof Error && (cause.name === "TimeoutError" || cause.name === "AbortError");
6282
6344
  }
6345
+ var UNREACHED_CODES = /* @__PURE__ */ new Set([
6346
+ "ENOTFOUND",
6347
+ "EAI_AGAIN",
6348
+ "ECONNREFUSED",
6349
+ "EHOSTUNREACH",
6350
+ "ENETUNREACH",
6351
+ "UND_ERR_CONNECT_TIMEOUT"
6352
+ ]);
6353
+ var TLS_CODE = /^(?:CERT_|ERR_TLS_|UNABLE_TO_|DEPTH_ZERO_|SELF_SIGNED_)/;
6354
+ function causeCode(cause) {
6355
+ const code = cause?.code;
6356
+ if (typeof code === "string") return code;
6357
+ const inner = cause instanceof AggregateError ? cause.errors[0] : void 0;
6358
+ const innerCode = inner?.code;
6359
+ return typeof innerCode === "string" ? innerCode : void 0;
6360
+ }
6361
+ function networkFailureResult(error) {
6362
+ if (!(error instanceof TypeError)) return void 0;
6363
+ const afterHeaders = error.message === "terminated";
6364
+ if (!afterHeaders && error.message !== "fetch failed") return void 0;
6365
+ const code = causeCode(error.cause);
6366
+ if (!code) return void 0;
6367
+ const unreached = !afterHeaders && (UNREACHED_CODES.has(code) || TLS_CODE.test(code));
6368
+ return {
6369
+ ok: false,
6370
+ status: unreached ? 503 : 502,
6371
+ payload: unreached ? {
6372
+ error: "Could not reach the Runpod API.",
6373
+ cause: code,
6374
+ 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."
6375
+ } : {
6376
+ error: "The connection to the Runpod API dropped before the full response arrived.",
6377
+ cause: code,
6378
+ 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."
6379
+ }
6380
+ };
6381
+ }
6283
6382
  async function runTool(body) {
6284
6383
  try {
6285
6384
  return await body();
@@ -6301,6 +6400,8 @@ async function runTool(body) {
6301
6400
  }
6302
6401
  };
6303
6402
  }
6403
+ const network = networkFailureResult(error);
6404
+ if (network) return network;
6304
6405
  throw error;
6305
6406
  }
6306
6407
  }
@@ -6371,6 +6472,7 @@ function rankByStock(rows, stats, includeUnavailable) {
6371
6472
  }
6372
6473
  var getCapacity = {
6373
6474
  name: "get-capacity",
6475
+ title: "Get GPU capacity by CUDA version",
6374
6476
  annotations: readOnly,
6375
6477
  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.",
6376
6478
  inputSchema: {
@@ -6572,6 +6674,7 @@ function formatGpuIds(gpu) {
6572
6674
  }
6573
6675
  var setEndpointGpus = {
6574
6676
  name: "set-endpoint-gpus",
6677
+ title: "Set endpoint GPUs",
6575
6678
  annotations: idempotentWrite,
6576
6679
  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.",
6577
6680
  inputSchema: {
@@ -6766,6 +6869,7 @@ function randomSuffix() {
6766
6869
  }
6767
6870
  var listHubRepos = {
6768
6871
  name: "list-hub-repos",
6872
+ title: "List Hub repos",
6769
6873
  annotations: readOnly,
6770
6874
  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.",
6771
6875
  inputSchema: {
@@ -6868,6 +6972,7 @@ var listHubRepos = {
6868
6972
  };
6869
6973
  var deployHubRepo = {
6870
6974
  name: "deploy-hub-repo",
6975
+ title: "Deploy Hub repo",
6871
6976
  // Two sequential upstream calls: together they can outlast a client's request timeout.
6872
6977
  holdsResponse: true,
6873
6978
  annotations: write,
@@ -7328,6 +7433,7 @@ function validateWait(wait) {
7328
7433
  }
7329
7434
  var runEndpoint = {
7330
7435
  name: "run-endpoint",
7436
+ title: "Submit Serverless job",
7331
7437
  annotations: write,
7332
7438
  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.",
7333
7439
  inputSchema: {
@@ -7358,6 +7464,7 @@ var runEndpoint = {
7358
7464
  };
7359
7465
  var runsyncEndpoint = {
7360
7466
  name: "runsync-endpoint",
7467
+ title: "Run Serverless job and wait",
7361
7468
  holdsResponse: true,
7362
7469
  annotations: write,
7363
7470
  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)."),
@@ -7403,6 +7510,7 @@ var runsyncEndpoint = {
7403
7510
  };
7404
7511
  var getJobStatus = {
7405
7512
  name: "get-job-status",
7513
+ title: "Get job status",
7406
7514
  holdsResponse: true,
7407
7515
  annotations: readOnly,
7408
7516
  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.",
@@ -7452,6 +7560,7 @@ var getJobStatus = {
7452
7560
  };
7453
7561
  var streamJob = {
7454
7562
  name: "stream-job",
7563
+ title: "Stream job output",
7455
7564
  holdsResponse: true,
7456
7565
  annotations: readOnly,
7457
7566
  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.`,
@@ -7484,6 +7593,7 @@ var streamJob = {
7484
7593
  };
7485
7594
  var cancelJob = {
7486
7595
  name: "cancel-job",
7596
+ title: "Cancel job",
7487
7597
  annotations: destructive,
7488
7598
  description: "Cancel a Serverless job that is queued or in progress.",
7489
7599
  inputSchema: {
@@ -7509,6 +7619,7 @@ var cancelJob = {
7509
7619
  };
7510
7620
  var retryJob = {
7511
7621
  name: "retry-job",
7622
+ title: "Retry job",
7512
7623
  annotations: write,
7513
7624
  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.",
7514
7625
  inputSchema: {
@@ -7534,6 +7645,7 @@ var retryJob = {
7534
7645
  };
7535
7646
  var endpointHealth = {
7536
7647
  name: "endpoint-health",
7648
+ title: "Get endpoint health",
7537
7649
  annotations: readOnly,
7538
7650
  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.",
7539
7651
  inputSchema: {
@@ -7554,6 +7666,7 @@ var endpointHealth = {
7554
7666
  };
7555
7667
  var purgeEndpointQueue = {
7556
7668
  name: "purge-endpoint-queue",
7669
+ title: "Purge endpoint queue",
7557
7670
  annotations: destructive,
7558
7671
  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.",
7559
7672
  inputSchema: {
@@ -7590,6 +7703,7 @@ var jobTools = [
7590
7703
  // src/specgen/tools/list-endpoints.ts
7591
7704
  var listEndpoints = {
7592
7705
  name: "list-endpoints",
7706
+ title: "List endpoints",
7593
7707
  annotations: readOnly,
7594
7708
  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.",
7595
7709
  inputSchema: {
@@ -7635,6 +7749,7 @@ var listEndpoints = {
7635
7749
  // src/specgen/tools/list-templates.ts
7636
7750
  var listTemplates = {
7637
7751
  name: "list-templates",
7752
+ title: "List templates",
7638
7753
  annotations: readOnly,
7639
7754
  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.",
7640
7755
  inputSchema: {
@@ -9076,6 +9191,7 @@ function logParams(args) {
9076
9191
  }
9077
9192
  var streamPodLogs = {
9078
9193
  name: "stream-pod-logs",
9194
+ title: "Stream Pod logs",
9079
9195
  holdsResponse: true,
9080
9196
  annotations: readOnly,
9081
9197
  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.",
@@ -9099,6 +9215,7 @@ var streamPodLogs = {
9099
9215
  };
9100
9216
  var streamWorkerLogs = {
9101
9217
  name: "stream-worker-logs",
9218
+ title: "Stream worker logs",
9102
9219
  holdsResponse: true,
9103
9220
  annotations: readOnly,
9104
9221
  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.",
@@ -9141,6 +9258,7 @@ function parseMetadata(metadata) {
9141
9258
  }
9142
9259
  var listPublicEndpoints = {
9143
9260
  name: "list-public-endpoints",
9261
+ title: "List public endpoints",
9144
9262
  annotations: readOnly,
9145
9263
  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.",
9146
9264
  inputSchema: {
@@ -9361,6 +9479,7 @@ var source = {
9361
9479
  };
9362
9480
  var listGuides = {
9363
9481
  name: "list-guides",
9482
+ title: "List guides",
9364
9483
  annotations: bundledRead,
9365
9484
  recordedArguments: ["kind", "query", "concept"],
9366
9485
  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.",
@@ -9397,6 +9516,7 @@ var listGuides = {
9397
9516
  };
9398
9517
  var readGuide = {
9399
9518
  name: "read-guide",
9519
+ title: "Read guide",
9400
9520
  annotations: bundledRead,
9401
9521
  recordedArguments: ["id"],
9402
9522
  recordedGuideKind: (args) => knowledge.guides.find((guide) => guide.id === String(args.id).trim())?.kind,
@@ -9436,6 +9556,7 @@ var readGuide = {
9436
9556
  };
9437
9557
  var lookupConcept = {
9438
9558
  name: "lookup-concept",
9559
+ title: "Look up a concept",
9439
9560
  annotations: bundledRead,
9440
9561
  recordedArguments: ["concept"],
9441
9562
  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".',
@@ -9489,6 +9610,7 @@ var lookupConcept = {
9489
9610
  };
9490
9611
  var searchConcepts = {
9491
9612
  name: "search-concepts",
9613
+ title: "Search concepts",
9492
9614
  annotations: bundledRead,
9493
9615
  recordedArguments: ["query", "limit"],
9494
9616
  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.',
@@ -9580,12 +9702,13 @@ function listedAnnotations(annotations) {
9580
9702
  return idempotentHint ? annotations : listed;
9581
9703
  }
9582
9704
  function listTool(tool) {
9583
- const { name, description, inputSchema } = tool;
9705
+ const { name, title, description, inputSchema } = tool;
9584
9706
  const annotations = listedAnnotations(tool.annotations);
9585
9707
  if (!tool.schemaOnDemand)
9586
- return { name, description, inputSchema, annotations };
9708
+ return { name, title, description, inputSchema, annotations };
9587
9709
  return {
9588
9710
  name,
9711
+ title,
9589
9712
  description: description + ON_DEMAND_POINTER,
9590
9713
  inputSchema: callShape(inputSchema),
9591
9714
  annotations
@@ -9637,6 +9760,7 @@ function schemaType(node, resolve2) {
9637
9760
  function createDescribeTool(served) {
9638
9761
  return {
9639
9762
  name: DESCRIBE_TOOL_NAME,
9763
+ title: "Describe tool",
9640
9764
  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.",
9641
9765
  inputSchema: {
9642
9766
  type: "object",
@@ -9774,6 +9898,24 @@ function captureResourceCall(apiKey, event) {
9774
9898
  );
9775
9899
  }
9776
9900
 
9901
+ // src/alp/sink.ts
9902
+ 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.";
9903
+ var ALP_REFERRAL_SOURCES = [
9904
+ "searched",
9905
+ "user_named",
9906
+ "recommended",
9907
+ "other"
9908
+ ];
9909
+ var ALP_REFERRAL_USE_CASES = [
9910
+ "inference",
9911
+ "fine_tuning",
9912
+ "training",
9913
+ "image_video_generation",
9914
+ "dev_environment",
9915
+ "batch_processing",
9916
+ "other"
9917
+ ];
9918
+
9777
9919
  // src/http.ts
9778
9920
  var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
9779
9921
  var import_types = require("@modelcontextprotocol/sdk/types.js");
@@ -9799,6 +9941,25 @@ function buildTrackingHeaders(input) {
9799
9941
  // src/specgen/clients/graphql.ts
9800
9942
  init_hosts();
9801
9943
  var DEFAULT_TIMEOUT_MS2 = 3e4;
9944
+ function graphqlErrorStatus(code) {
9945
+ switch (code) {
9946
+ case "UNAUTHENTICATED":
9947
+ return 401;
9948
+ case "UNAUTHORIZED":
9949
+ case "FORBIDDEN":
9950
+ return 403;
9951
+ // Runpod's own code for a rejected request: an unknown GPU pool, a missing
9952
+ // template, no balance. The caller has to change the input, so it is a 400
9953
+ // and gets the 400 detail hints, not a retry-the-same-call 502.
9954
+ case "RUNPOD":
9955
+ case "BAD_USER_INPUT":
9956
+ case "GRAPHQL_VALIDATION_FAILED":
9957
+ case "GRAPHQL_PARSE_FAILED":
9958
+ return 400;
9959
+ default:
9960
+ return 502;
9961
+ }
9962
+ }
9802
9963
  function createGraphqlClient(options = {}) {
9803
9964
  const apiKey = options.apiKey ?? process.env.RUNPOD_API_KEY;
9804
9965
  const publicUrl = options.publicUrl ?? publicGraphqlBase(process.env);
@@ -9822,15 +9983,20 @@ function createGraphqlClient(options = {}) {
9822
9983
  );
9823
9984
  }
9824
9985
  const result = await response.json();
9825
- if (result.errors && result.errors.length > 0) {
9986
+ if (Array.isArray(result.errors) && result.errors.length > 0) {
9987
+ const errors = result.errors;
9826
9988
  throw new HttpError(
9827
- `GraphQL error: ${result.errors.map((e) => e.message).join(", ")}`,
9828
- response.status,
9829
- result.errors
9989
+ `GraphQL error: ${errors.map((e) => String(e?.message ?? JSON.stringify(e))).join(", ")}`,
9990
+ graphqlErrorStatus(errors[0]?.extensions?.code),
9991
+ errors
9830
9992
  );
9831
9993
  }
9832
9994
  if (result.data === void 0 || result.data === null) {
9833
- throw new HttpError("GraphQL response carried no data", response.status);
9995
+ throw new HttpError(
9996
+ "GraphQL response carried no data",
9997
+ 502,
9998
+ result.errors
9999
+ );
9834
10000
  }
9835
10001
  return result.data;
9836
10002
  }
@@ -10157,12 +10323,21 @@ async function submit(ctx, ingestUrl, route, args, extra) {
10157
10323
  tool: args.tool,
10158
10324
  workaround: args.workaround,
10159
10325
  trigger: args.trigger,
10326
+ source: args.source,
10327
+ noContext: args.noContext,
10328
+ useCase: args.useCase,
10160
10329
  ...extra
10161
10330
  }),
10162
10331
  signal: AbortSignal.timeout(1e4)
10163
10332
  });
10164
10333
  const body = await response.json().catch(() => ({}));
10165
- return { recorded: response.ok && body.recorded === true };
10334
+ return {
10335
+ recorded: response.ok && body.recorded === true,
10336
+ // The ingest's own named outcome (already_submitted on a once-per-account
10337
+ // route), carried through so the tool can say it. A string from a 200
10338
+ // only — never an upstream error body.
10339
+ ...response.ok && typeof body.reason === "string" ? { reason: body.reason } : {}
10340
+ };
10166
10341
  } catch {
10167
10342
  return { recorded: false };
10168
10343
  }
@@ -10193,6 +10368,10 @@ async function readJournal(ctx, readUrl, limit) {
10193
10368
  }
10194
10369
  }
10195
10370
  var SEVERITY = ["blocked", "degraded", "cosmetic"];
10371
+ var MODEL_TYPE_FIELD = {
10372
+ type: "string",
10373
+ 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.'
10374
+ };
10196
10375
  var TOOL_FIELD = {
10197
10376
  type: "string",
10198
10377
  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."
@@ -10209,10 +10388,7 @@ var alpInputSchema = (contentDescription, extra = {}) => ({
10209
10388
  description: "One short sentence: what you were trying to accomplish when this came up."
10210
10389
  },
10211
10390
  ...extra,
10212
- modelType: {
10213
- type: "string",
10214
- 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.'
10215
- }
10391
+ modelType: MODEL_TYPE_FIELD
10216
10392
  },
10217
10393
  required: ["content"]
10218
10394
  });
@@ -10227,6 +10403,7 @@ function createAlpTools(opts) {
10227
10403
  // is ignored, never a 400 that derails the task the agent is actually on.
10228
10404
  lenientArguments: true,
10229
10405
  name: "report_feedback",
10406
+ title: "Report feedback",
10230
10407
  annotations: write,
10231
10408
  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.",
10232
10409
  inputSchema: alpInputSchema(
@@ -10263,6 +10440,7 @@ function createAlpTools(opts) {
10263
10440
  // is ignored, never a 400 that derails the task the agent is actually on.
10264
10441
  lenientArguments: true,
10265
10442
  name: "save_to_journal",
10443
+ title: "Save to journal",
10266
10444
  annotations: write,
10267
10445
  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.",
10268
10446
  inputSchema: alpInputSchema(
@@ -10294,6 +10472,7 @@ function createAlpTools(opts) {
10294
10472
  // is ignored, never a 400 that derails the task the agent is actually on.
10295
10473
  lenientArguments: true,
10296
10474
  name: "ask_question",
10475
+ title: "Ask a question",
10297
10476
  annotations: write,
10298
10477
  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.",
10299
10478
  inputSchema: alpInputSchema(
@@ -10319,6 +10498,7 @@ function createAlpTools(opts) {
10319
10498
  const readJournalTool = {
10320
10499
  lenientArguments: true,
10321
10500
  name: "read_journal",
10501
+ title: "Read journal",
10322
10502
  annotations: readOnly,
10323
10503
  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.",
10324
10504
  inputSchema: {
@@ -10346,13 +10526,68 @@ function createAlpTools(opts) {
10346
10526
  });
10347
10527
  }
10348
10528
  };
10349
- return [reportFeedback, saveToJournal, askQuestion, readJournalTool];
10529
+ const reportHowUserFoundRunpod = {
10530
+ lenientArguments: true,
10531
+ name: "report_how_user_found_runpod",
10532
+ title: "Report how the user found Runpod",
10533
+ annotations: idempotentWrite,
10534
+ 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.",
10535
+ inputSchema: {
10536
+ type: "object",
10537
+ properties: {
10538
+ content: {
10539
+ type: "string",
10540
+ 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.'
10541
+ },
10542
+ source: {
10543
+ type: "string",
10544
+ enum: [...ALP_REFERRAL_SOURCES],
10545
+ 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."
10546
+ },
10547
+ useCase: {
10548
+ type: "string",
10549
+ enum: [...ALP_REFERRAL_USE_CASES],
10550
+ 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."
10551
+ },
10552
+ noContext: {
10553
+ type: "boolean",
10554
+ 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."
10555
+ },
10556
+ modelType: MODEL_TYPE_FIELD
10557
+ },
10558
+ required: []
10559
+ },
10560
+ handler: async (ctx, args) => {
10561
+ const { recorded, reason } = await submit(
10562
+ ctx,
10563
+ opts.ingestUrl,
10564
+ "referral",
10565
+ args,
10566
+ extra()
10567
+ );
10568
+ if (reason === "already_submitted") {
10569
+ return ok({ recorded: false, reason, note: ALREADY_SUBMITTED_NOTE });
10570
+ }
10571
+ return ok({
10572
+ recorded,
10573
+ note: recorded ? "Recorded, thank you. Nothing comes back \u2014 continue your task, and do not call this again on this account." : NOT_RECORDED
10574
+ });
10575
+ }
10576
+ };
10577
+ return [
10578
+ reportFeedback,
10579
+ saveToJournal,
10580
+ askQuestion,
10581
+ readJournalTool,
10582
+ reportHowUserFoundRunpod
10583
+ ];
10350
10584
  }
10351
10585
 
10352
10586
  // src/specgen/tools/search.ts
10353
10587
  function createSearchDocsTool(search) {
10354
10588
  return {
10355
10589
  name: "search-docs",
10590
+ title: "Search docs",
10356
10591
  annotations: readOnly,
10357
10592
  recordedArguments: ["limit"],
10358
10593
  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.',
@@ -10427,7 +10662,10 @@ THE OFFICIAL RUNPOD PLUGIN. Runpod ships an agent plugin that also covers the la
10427
10662
  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.`;
10428
10663
  var ALP_INSTRUCTIONS = `
10429
10664
 
10430
- 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.`;
10665
+ 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.`;
10666
+ var REFERRAL_INSTRUCTIONS = `
10667
+
10668
+ 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.`;
10431
10669
  function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
10432
10670
  const rateLimiter = opts.rateLimiter ?? noopRateLimiter;
10433
10671
  const caller = callerId(ctx.apiKey);
@@ -10444,7 +10682,7 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
10444
10682
  { name: SERVER_NAME, version: `${version} [specgen]` },
10445
10683
  {
10446
10684
  capabilities: { tools: {}, resources: {} },
10447
- instructions: opts.alp ? SERVER_INSTRUCTIONS + ALP_INSTRUCTIONS : SERVER_INSTRUCTIONS
10685
+ instructions: opts.alp ? SERVER_INSTRUCTIONS + ALP_INSTRUCTIONS + (opts.promptReferral === false ? "" : REFERRAL_INSTRUCTIONS) : SERVER_INSTRUCTIONS
10448
10686
  }
10449
10687
  );
10450
10688
  server.oninitialized = () => {