@aws/ml-container-creator 0.10.0 → 0.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/LICENSE-THIRD-PARTY +9304 -0
  2. package/bin/cli.js +2 -0
  3. package/config/bootstrap-e2e-stack.json +341 -0
  4. package/config/bootstrap-stack.json +40 -3
  5. package/config/parameter-schema-v2.json +5 -21
  6. package/config/tune-catalog.json +1781 -0
  7. package/infra/ci-harness/buildspec.yml +1 -0
  8. package/infra/ci-harness/lambda/path-prover/brain.ts +306 -0
  9. package/infra/ci-harness/lambda/path-prover/write-results.ts +152 -0
  10. package/infra/ci-harness/lib/ci-harness-stack.ts +837 -7
  11. package/infra/ci-harness/state-machines/path-prover.asl.json +496 -0
  12. package/package.json +51 -66
  13. package/servers/base-image-picker/index.js +121 -121
  14. package/servers/e2e-status/index.js +297 -0
  15. package/servers/e2e-status/manifest.json +14 -0
  16. package/servers/e2e-status/package.json +15 -0
  17. package/servers/endpoint-picker/LICENSE +202 -0
  18. package/servers/endpoint-picker/index.js +536 -0
  19. package/servers/endpoint-picker/manifest.json +14 -0
  20. package/servers/endpoint-picker/package.json +18 -0
  21. package/servers/hyperpod-cluster-picker/index.js +125 -125
  22. package/servers/instance-sizer/index.js +138 -138
  23. package/servers/instance-sizer/lib/instance-ranker.js +76 -76
  24. package/servers/instance-sizer/lib/model-resolver.js +61 -61
  25. package/servers/instance-sizer/lib/quota-resolver.js +113 -113
  26. package/servers/instance-sizer/lib/vram-estimator.js +31 -31
  27. package/servers/lib/bedrock-client.js +38 -38
  28. package/servers/lib/catalogs/model-servers.json +201 -3
  29. package/servers/lib/custom-validators.js +13 -13
  30. package/servers/lib/dynamic-resolver.js +4 -4
  31. package/servers/marketplace-picker/index.js +342 -0
  32. package/servers/marketplace-picker/manifest.json +14 -0
  33. package/servers/marketplace-picker/package.json +18 -0
  34. package/servers/model-picker/index.js +382 -382
  35. package/servers/region-picker/index.js +56 -56
  36. package/servers/workload-picker/LICENSE +202 -0
  37. package/servers/workload-picker/catalogs/workload-profiles.json +67 -0
  38. package/servers/workload-picker/index.js +171 -0
  39. package/servers/workload-picker/manifest.json +16 -0
  40. package/servers/workload-picker/package.json +16 -0
  41. package/src/app.js +4 -2
  42. package/src/lib/bootstrap-command-handler.js +579 -14
  43. package/src/lib/bootstrap-config.js +36 -0
  44. package/src/lib/bootstrap-profile-manager.js +48 -41
  45. package/src/lib/ci-register-helpers.js +74 -0
  46. package/src/lib/config-loader.js +3 -0
  47. package/src/lib/config-manager.js +7 -0
  48. package/src/lib/cuda-resolver.js +17 -8
  49. package/src/lib/generated/cli-options.js +315 -315
  50. package/src/lib/generated/parameter-matrix.js +661 -661
  51. package/src/lib/generated/validation-rules.js +71 -71
  52. package/src/lib/path-prover-brain.js +607 -0
  53. package/src/lib/prompts/project-prompts.js +12 -0
  54. package/src/lib/template-variable-resolver.js +25 -1
  55. package/src/lib/tune-catalog-validator.js +37 -4
  56. package/templates/Dockerfile +9 -0
  57. package/templates/code/adapter_sidecar.py +444 -0
  58. package/templates/code/serve +6 -0
  59. package/templates/code/serve.d/vllm.ejs +1 -1
  60. package/templates/do/.benchmark_writer.py +1476 -0
  61. package/templates/do/.tune_helper.py +982 -57
  62. package/templates/do/__pycache__/.benchmark_writer.cpython-312.pyc +0 -0
  63. package/templates/do/adapter +149 -0
  64. package/templates/do/benchmark +639 -85
  65. package/templates/do/config +108 -5
  66. package/templates/do/deploy.d/managed-inference.ejs +192 -11
  67. package/templates/do/optimize +106 -37
  68. package/templates/do/register +89 -0
  69. package/templates/do/test +13 -0
  70. package/templates/do/tune +378 -59
  71. package/templates/do/validate +44 -4
@@ -19,20 +19,20 @@
19
19
  * AWS_REGION - AWS region for SageMaker API calls (default: us-east-1)
20
20
  */
21
21
 
22
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
23
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
24
- import { z } from 'zod'
25
- import { fileURLToPath } from 'node:url'
26
- import { resolve } from 'node:path'
27
- import { readFileSync } from 'node:fs'
28
- import { homedir } from 'node:os'
29
- import { DynamicResolver } from '../lib/dynamic-resolver.js'
22
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
23
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
24
+ import { z } from 'zod';
25
+ import { fileURLToPath } from 'node:url';
26
+ import { resolve } from 'node:path';
27
+ import { readFileSync } from 'node:fs';
28
+ import { homedir } from 'node:os';
29
+ import { DynamicResolver } from '../lib/dynamic-resolver.js';
30
30
 
31
31
  /**
32
32
  * Log to stderr so it doesn't interfere with MCP stdio protocol on stdout.
33
33
  */
34
34
  function log(message) {
35
- process.stderr.write(`[hyperpod-cluster-picker] ${message}\n`)
35
+ process.stderr.write(`[hyperpod-cluster-picker] ${message}\n`);
36
36
  }
37
37
 
38
38
  /**
@@ -46,14 +46,14 @@ function log(message) {
46
46
  * @returns {object} SageMaker client
47
47
  */
48
48
  function createSageMakerClient(region, clientFactory = null) {
49
- if (clientFactory) return clientFactory(region)
50
- return _defaultClientFactory(region)
49
+ if (clientFactory) return clientFactory(region);
50
+ return _defaultClientFactory(region);
51
51
  }
52
52
 
53
- let _SageMakerClient = null
54
- let _ListClustersCommand = null
55
- let _DescribeClusterCommand = null
56
- let _fromIni = null
53
+ let _SageMakerClient = null;
54
+ let _ListClustersCommand = null;
55
+ let _DescribeClusterCommand = null;
56
+ let _fromIni = null;
57
57
 
58
58
  /**
59
59
  * Lazily load the AWS SDK SageMaker client classes.
@@ -61,21 +61,21 @@ let _fromIni = null
61
61
  * without requiring @aws-sdk/client-sagemaker to be installed.
62
62
  */
63
63
  async function _ensureSdkLoaded() {
64
- if (_SageMakerClient) return
65
- const sdk = await import('@aws-sdk/client-sagemaker')
66
- _SageMakerClient = sdk.SageMakerClient
67
- _ListClustersCommand = sdk.ListClustersCommand
68
- _DescribeClusterCommand = sdk.DescribeClusterCommand
64
+ if (_SageMakerClient) return;
65
+ const sdk = await import('@aws-sdk/client-sagemaker');
66
+ _SageMakerClient = sdk.SageMakerClient;
67
+ _ListClustersCommand = sdk.ListClustersCommand;
68
+ _DescribeClusterCommand = sdk.DescribeClusterCommand;
69
69
  try {
70
- const credentialProviders = await import('@aws-sdk/credential-providers')
71
- _fromIni = credentialProviders.fromIni
70
+ const credentialProviders = await import('@aws-sdk/credential-providers');
71
+ _fromIni = credentialProviders.fromIni;
72
72
  } catch {
73
73
  // credential-providers not available — profile-based fallback won't work
74
74
  }
75
75
  }
76
76
 
77
77
  function _defaultClientFactory(region) {
78
- return new _SageMakerClient({ region })
78
+ return new _SageMakerClient({ region });
79
79
  }
80
80
 
81
81
  /**
@@ -86,12 +86,12 @@ function _defaultClientFactory(region) {
86
86
  */
87
87
  function _createClientWithProfile(region, profile) {
88
88
  if (!_fromIni) {
89
- throw new Error('Cannot use profile-based credentials: @aws-sdk/credential-providers not available')
89
+ throw new Error('Cannot use profile-based credentials: @aws-sdk/credential-providers not available');
90
90
  }
91
91
  return new _SageMakerClient({
92
92
  region,
93
93
  credentials: _fromIni({ profile })
94
- })
94
+ });
95
95
  }
96
96
 
97
97
  /**
@@ -99,22 +99,22 @@ function _createClientWithProfile(region, profile) {
99
99
  * @returns {string[]} Array of profile names
100
100
  */
101
101
  function _detectAwsProfiles() {
102
- const profiles = new Set()
102
+ const profiles = new Set();
103
103
  try {
104
- const credsPath = resolve(homedir(), '.aws/credentials')
105
- const creds = readFileSync(credsPath, 'utf8')
104
+ const credsPath = resolve(homedir(), '.aws/credentials');
105
+ const creds = readFileSync(credsPath, 'utf8');
106
106
  for (const match of creds.matchAll(/^\[(.+)\]$/gm)) {
107
- profiles.add(match[1])
107
+ profiles.add(match[1]);
108
108
  }
109
109
  } catch { /* no credentials file */ }
110
110
  try {
111
- const configPath = resolve(homedir(), '.aws/config')
112
- const config = readFileSync(configPath, 'utf8')
111
+ const configPath = resolve(homedir(), '.aws/config');
112
+ const config = readFileSync(configPath, 'utf8');
113
113
  for (const match of config.matchAll(/^\[profile\s+(.+)\]$/gm)) {
114
- profiles.add(match[1])
114
+ profiles.add(match[1]);
115
115
  }
116
116
  } catch { /* no config file */ }
117
- return [...profiles]
117
+ return [...profiles];
118
118
  }
119
119
 
120
120
  /**
@@ -125,65 +125,65 @@ function _detectAwsProfiles() {
125
125
  * @returns {Promise<Array<{ clusterName: string, clusterArn: string, status: string, instanceGroups: Array }>>}
126
126
  */
127
127
  async function fetchHyperPodClusters(client, { limit = 10 } = {}) {
128
- const clusters = []
129
- let nextToken
128
+ const clusters = [];
129
+ let nextToken;
130
130
 
131
131
  // Paginate through ListClusters
132
132
  do {
133
- const params = { MaxResults: 100 }
134
- if (nextToken) params.NextToken = nextToken
133
+ const params = { MaxResults: 100 };
134
+ if (nextToken) params.NextToken = nextToken;
135
135
 
136
- const command = new _ListClustersCommand(params)
137
- const response = await client.send(command)
136
+ const command = new _ListClustersCommand(params);
137
+ const response = await client.send(command);
138
138
 
139
- const summaries = response.ClusterSummaries || []
139
+ const summaries = response.ClusterSummaries || [];
140
140
  for (const summary of summaries) {
141
141
  // Filter: InService only
142
- if (summary.ClusterStatus !== 'InService') continue
142
+ if (summary.ClusterStatus !== 'InService') continue;
143
143
 
144
144
  clusters.push({
145
145
  clusterName: summary.ClusterName,
146
146
  clusterArn: summary.ClusterArn,
147
147
  status: summary.ClusterStatus
148
- })
148
+ });
149
149
  }
150
150
 
151
- nextToken = response.NextToken
152
- } while (nextToken && clusters.length < limit * 3) // over-fetch to account for EKS filtering
151
+ nextToken = response.NextToken;
152
+ } while (nextToken && clusters.length < limit * 3); // over-fetch to account for EKS filtering
153
153
 
154
154
  // Now describe each cluster to check orchestrator type and get instance groups
155
- const eksClusters = []
155
+ const eksClusters = [];
156
156
  for (const cluster of clusters) {
157
- if (eksClusters.length >= limit) break
157
+ if (eksClusters.length >= limit) break;
158
158
 
159
159
  try {
160
160
  const describeCommand = new _DescribeClusterCommand({
161
161
  ClusterName: cluster.clusterName
162
- })
163
- const detail = await client.send(describeCommand)
162
+ });
163
+ const detail = await client.send(describeCommand);
164
164
 
165
165
  // Filter: EKS orchestrator only (exclude Slurm)
166
- const orchestrator = detail.Orchestrator?.Eks ? 'EKS' : 'Slurm'
167
- if (orchestrator !== 'EKS') continue
166
+ const orchestrator = detail.Orchestrator?.Eks ? 'EKS' : 'Slurm';
167
+ if (orchestrator !== 'EKS') continue;
168
168
 
169
169
  const instanceGroups = (detail.InstanceGroups || []).map(g => ({
170
170
  name: g.InstanceGroupName,
171
171
  instanceType: g.InstanceType,
172
172
  count: g.CurrentCount ?? g.TargetCount ?? 0
173
- }))
173
+ }));
174
174
 
175
175
  eksClusters.push({
176
176
  clusterName: cluster.clusterName,
177
177
  clusterArn: cluster.clusterArn,
178
178
  status: cluster.status,
179
179
  instanceGroups
180
- })
180
+ });
181
181
  } catch (err) {
182
- log(`Warning: could not describe cluster "${cluster.clusterName}": ${err.message}`)
182
+ log(`Warning: could not describe cluster "${cluster.clusterName}": ${err.message}`);
183
183
  }
184
184
  }
185
185
 
186
- return eksClusters
186
+ return eksClusters;
187
187
  }
188
188
 
189
189
  /**
@@ -198,10 +198,10 @@ function buildResponse(clusters) {
198
198
  values: {},
199
199
  choices: { hyperPodCluster: [] },
200
200
  message: 'No InService HyperPod EKS clusters found in the specified region. Verify the region and that you have HyperPod EKS clusters provisioned.'
201
- }
201
+ };
202
202
  }
203
203
 
204
- const clusterNames = clusters.map(c => c.clusterName)
204
+ const clusterNames = clusters.map(c => c.clusterName);
205
205
 
206
206
  return {
207
207
  values: { hyperPodCluster: clusterNames[0] },
@@ -213,7 +213,7 @@ function buildResponse(clusters) {
213
213
  instanceGroups: c.instanceGroups
214
214
  }])
215
215
  )
216
- }
216
+ };
217
217
  }
218
218
 
219
219
  // ── ClusterResolver ──────────────────────────────────────────────────────────
@@ -226,70 +226,70 @@ function buildResponse(clusters) {
226
226
  */
227
227
  class ClusterResolver extends DynamicResolver {
228
228
  constructor(options = {}) {
229
- super()
230
- this._region = options.region || process.env.AWS_REGION || 'us-east-1'
231
- this._profile = options.profile || process.env.AWS_PROFILE || null
232
- this._clientFactory = options.clientFactory || null
229
+ super();
230
+ this._region = options.region || process.env.AWS_REGION || 'us-east-1';
231
+ this._profile = options.profile || process.env.AWS_PROFILE || null;
232
+ this._clientFactory = options.clientFactory || null;
233
233
  }
234
234
 
235
235
  async fetch(key, options = {}) {
236
- const { limit = 10 } = options
236
+ const { limit = 10 } = options;
237
237
 
238
- await _ensureSdkLoaded()
238
+ await _ensureSdkLoaded();
239
239
 
240
- let clusters = null
241
- let lastError = null
240
+ let clusters = null;
241
+ let lastError = null;
242
242
 
243
243
  // Strategy 1: If a specific profile was requested, use it directly
244
244
  if (this._profile) {
245
245
  try {
246
- const client = _createClientWithProfile(this._region, this._profile)
247
- clusters = await fetchHyperPodClusters(client, { limit })
246
+ const client = _createClientWithProfile(this._region, this._profile);
247
+ clusters = await fetchHyperPodClusters(client, { limit });
248
248
  } catch (err) {
249
- log(`Profile "${this._profile}" failed: ${err.message}`)
250
- lastError = err
249
+ log(`Profile "${this._profile}" failed: ${err.message}`);
250
+ lastError = err;
251
251
  }
252
252
  }
253
253
 
254
254
  // Strategy 2: Try the default credential chain
255
255
  if (!clusters) {
256
256
  try {
257
- const client = createSageMakerClient(this._region, this._clientFactory)
258
- clusters = await fetchHyperPodClusters(client, { limit })
257
+ const client = createSageMakerClient(this._region, this._clientFactory);
258
+ clusters = await fetchHyperPodClusters(client, { limit });
259
259
  } catch (err) {
260
- log(`Default credential chain failed: ${err.message}`)
261
- lastError = err
260
+ log(`Default credential chain failed: ${err.message}`);
261
+ lastError = err;
262
262
  }
263
263
  }
264
264
 
265
265
  // Strategy 3: Detect available AWS profiles and try each
266
266
  if (!clusters && _fromIni) {
267
- const profiles = _detectAwsProfiles()
267
+ const profiles = _detectAwsProfiles();
268
268
  for (const p of profiles) {
269
269
  try {
270
- const client = _createClientWithProfile(this._region, p)
271
- clusters = await fetchHyperPodClusters(client, { limit })
272
- log(`Profile "${p}" succeeded`)
273
- break
270
+ const client = _createClientWithProfile(this._region, p);
271
+ clusters = await fetchHyperPodClusters(client, { limit });
272
+ log(`Profile "${p}" succeeded`);
273
+ break;
274
274
  } catch (err) {
275
- log(`Profile "${p}" failed: ${err.message}`)
276
- lastError = err
275
+ log(`Profile "${p}" failed: ${err.message}`);
276
+ lastError = err;
277
277
  }
278
278
  }
279
279
  }
280
280
 
281
281
  if (!clusters) {
282
- throw lastError || new Error('No AWS credentials available')
282
+ throw lastError || new Error('No AWS credentials available');
283
283
  }
284
284
 
285
285
  return {
286
286
  items: clusters,
287
287
  defaultItem: clusters[0] || null
288
- }
288
+ };
289
289
  }
290
290
 
291
291
  supportedKeys() {
292
- return ['hyperPodCluster']
292
+ return ['hyperPodCluster'];
293
293
  }
294
294
  }
295
295
 
@@ -297,7 +297,7 @@ class ClusterResolver extends DynamicResolver {
297
297
  const server = new McpServer({
298
298
  name: 'hyperpod-cluster-picker',
299
299
  version: '1.0.0'
300
- })
300
+ });
301
301
 
302
302
  // Register the get_hyperpod_clusters tool
303
303
  server.tool(
@@ -316,57 +316,57 @@ server.tool(
316
316
  type: 'text',
317
317
  text: JSON.stringify({ values: {}, choices: {} })
318
318
  }]
319
- }
319
+ };
320
320
  }
321
321
 
322
- const region = context?.awsRegion || process.env.AWS_REGION || 'us-east-1'
323
- const profile = context?.awsProfile || process.env.AWS_PROFILE || null
324
- log(`Querying HyperPod clusters in region: ${region}${profile ? ` (profile: ${profile})` : ''}`)
322
+ const region = context?.awsRegion || process.env.AWS_REGION || 'us-east-1';
323
+ const profile = context?.awsProfile || process.env.AWS_PROFILE || null;
324
+ log(`Querying HyperPod clusters in region: ${region}${profile ? ` (profile: ${profile})` : ''}`);
325
325
 
326
326
  try {
327
- await _ensureSdkLoaded()
327
+ await _ensureSdkLoaded();
328
328
 
329
- let clusters = null
330
- let lastError = null
329
+ let clusters = null;
330
+ let lastError = null;
331
331
 
332
332
  // Strategy 1: If a specific profile was requested, use it directly
333
333
  if (profile) {
334
334
  try {
335
- log(`Trying explicit profile: ${profile}`)
336
- const client = _createClientWithProfile(region, profile)
337
- clusters = await fetchHyperPodClusters(client, { limit })
335
+ log(`Trying explicit profile: ${profile}`);
336
+ const client = _createClientWithProfile(region, profile);
337
+ clusters = await fetchHyperPodClusters(client, { limit });
338
338
  } catch (err) {
339
- log(`Profile "${profile}" failed: ${err.message}`)
340
- lastError = err
339
+ log(`Profile "${profile}" failed: ${err.message}`);
340
+ lastError = err;
341
341
  }
342
342
  }
343
343
 
344
344
  // Strategy 2: Try the default credential chain (env vars, instance profile, etc.)
345
345
  if (!clusters) {
346
346
  try {
347
- log('Trying default credential chain')
348
- const client = createSageMakerClient(region)
349
- clusters = await fetchHyperPodClusters(client, { limit })
347
+ log('Trying default credential chain');
348
+ const client = createSageMakerClient(region);
349
+ clusters = await fetchHyperPodClusters(client, { limit });
350
350
  } catch (err) {
351
- log(`Default credential chain failed: ${err.message}`)
352
- lastError = err
351
+ log(`Default credential chain failed: ${err.message}`);
352
+ lastError = err;
353
353
  }
354
354
  }
355
355
 
356
356
  // Strategy 3: Detect available AWS profiles and try each
357
357
  if (!clusters && _fromIni) {
358
- const profiles = _detectAwsProfiles()
358
+ const profiles = _detectAwsProfiles();
359
359
  if (profiles.length > 0) {
360
- log(`Default credentials failed, trying ${profiles.length} detected profile(s): ${profiles.join(', ')}`)
360
+ log(`Default credentials failed, trying ${profiles.length} detected profile(s): ${profiles.join(', ')}`);
361
361
  for (const p of profiles) {
362
362
  try {
363
- const client = _createClientWithProfile(region, p)
364
- clusters = await fetchHyperPodClusters(client, { limit })
365
- log(`Profile "${p}" succeeded`)
366
- break
363
+ const client = _createClientWithProfile(region, p);
364
+ clusters = await fetchHyperPodClusters(client, { limit });
365
+ log(`Profile "${p}" succeeded`);
366
+ break;
367
367
  } catch (err) {
368
- log(`Profile "${p}" failed: ${err.message}`)
369
- lastError = err
368
+ log(`Profile "${p}" failed: ${err.message}`);
369
+ lastError = err;
370
370
  }
371
371
  }
372
372
  }
@@ -374,15 +374,15 @@ server.tool(
374
374
 
375
375
  // If all strategies failed, throw the last error
376
376
  if (!clusters) {
377
- throw lastError || new Error('No AWS credentials available')
377
+ throw lastError || new Error('No AWS credentials available');
378
378
  }
379
379
 
380
- const result = buildResponse(clusters)
380
+ const result = buildResponse(clusters);
381
381
 
382
382
  if (clusters.length > 0) {
383
- log(`Found ${clusters.length} HyperPod EKS cluster(s)`)
383
+ log(`Found ${clusters.length} HyperPod EKS cluster(s)`);
384
384
  } else {
385
- log('No InService HyperPod EKS clusters found')
385
+ log('No InService HyperPod EKS clusters found');
386
386
  }
387
387
 
388
388
  return {
@@ -390,35 +390,35 @@ server.tool(
390
390
  type: 'text',
391
391
  text: JSON.stringify(result)
392
392
  }]
393
- }
393
+ };
394
394
  } catch (err) {
395
- log(`Error querying clusters: ${err.message}`)
395
+ log(`Error querying clusters: ${err.message}`);
396
396
  const errorResult = {
397
397
  values: {},
398
398
  choices: { hyperPodCluster: [] },
399
399
  error: err.message,
400
400
  message: `Failed to query HyperPod clusters: ${err.message}`
401
- }
401
+ };
402
402
  return {
403
403
  content: [{
404
404
  type: 'text',
405
405
  text: JSON.stringify(errorResult)
406
406
  }]
407
- }
407
+ };
408
408
  }
409
409
  }
410
- )
410
+ );
411
411
 
412
412
  // Export for testing
413
- export { fetchHyperPodClusters, buildResponse, createSageMakerClient, _ensureSdkLoaded, ClusterResolver }
413
+ export { fetchHyperPodClusters, buildResponse, createSageMakerClient, _ensureSdkLoaded, ClusterResolver };
414
414
 
415
415
  // Guard MCP transport — only connect when run as main module
416
- const __filename = fileURLToPath(import.meta.url)
417
- const isMain = process.argv[1] && resolve(process.argv[1]) === __filename
416
+ const __filename = fileURLToPath(import.meta.url);
417
+ const isMain = process.argv[1] && resolve(process.argv[1]) === __filename;
418
418
 
419
419
  if (isMain) {
420
- log('Starting HyperPod Cluster Picker MCP server')
421
- await _ensureSdkLoaded()
422
- const transport = new StdioServerTransport()
423
- await server.connect(transport)
420
+ log('Starting HyperPod Cluster Picker MCP server');
421
+ await _ensureSdkLoaded();
422
+ const transport = new StdioServerTransport();
423
+ await server.connect(transport);
424
424
  }