@mintmcp/hosted-cli 0.0.18 → 0.0.20
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/api.d.ts +36 -13
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +13 -3
- package/dist/api.js.map +1 -1
- package/dist/containerBuilder.d.ts.map +1 -1
- package/dist/containerBuilder.js +19 -81
- package/dist/containerBuilder.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +134 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/api.ts +14 -3
- package/src/containerBuilder.ts +33 -95
- package/src/index.ts +199 -25
package/src/containerBuilder.ts
CHANGED
|
@@ -2,6 +2,9 @@ import { type ChildProcess, execFileSync, spawn } from "node:child_process";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import net from "node:net";
|
|
4
4
|
import { setTimeout as delay } from "node:timers/promises";
|
|
5
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
7
|
+
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
5
8
|
|
|
6
9
|
export interface BuildOptions {
|
|
7
10
|
dockerfile?: string;
|
|
@@ -66,10 +69,7 @@ const SIZE_REJECT_BYTES = 1024 * 1024 * 1024;
|
|
|
66
69
|
const DEFAULT_BUILD_PLATFORM = "linux/amd64";
|
|
67
70
|
const DEFAULT_PROBE_PATH = "/mcp";
|
|
68
71
|
const DEFAULT_PROBE_TIMEOUT_MS = 30_000;
|
|
69
|
-
const LOCAL_TEST_REQUEST_TIMEOUT_MS = 5_000;
|
|
70
72
|
const LOCAL_TEST_IMAGE_PREFIX = "mintmcp-local-test";
|
|
71
|
-
const LOCAL_TEST_PROBE_ID = "mintmcp-local-test";
|
|
72
|
-
const LOCAL_TEST_PROTOCOL_VERSION = "2025-06-18";
|
|
73
73
|
const LOCAL_TEST_POLL_INTERVAL_MS = 500;
|
|
74
74
|
|
|
75
75
|
export function defaultBuildPlatform(): string {
|
|
@@ -273,18 +273,14 @@ export async function smokeTestImage(
|
|
|
273
273
|
const { logs, exitInfo } = captureContainerLogs(container);
|
|
274
274
|
|
|
275
275
|
try {
|
|
276
|
-
await
|
|
277
|
-
{
|
|
278
|
-
url,
|
|
279
|
-
probeTimeoutMs,
|
|
280
|
-
},
|
|
276
|
+
await mcpSmokeTest(
|
|
277
|
+
{ url, probeTimeoutMs },
|
|
281
278
|
{
|
|
282
279
|
fetch: deps.fetch,
|
|
283
280
|
sleep: deps.sleep,
|
|
284
281
|
getExitInfo: () => exitInfo.value,
|
|
285
282
|
},
|
|
286
283
|
);
|
|
287
|
-
console.log(`Local MCP probe succeeded: ${url}`);
|
|
288
284
|
return { containerName, imageRef: options.imageRef, url };
|
|
289
285
|
} catch (error) {
|
|
290
286
|
const message = formatSmokeTestFailureMessage(error, logs.join(""));
|
|
@@ -426,7 +422,7 @@ function captureContainerLogs(container: ChildProcess): {
|
|
|
426
422
|
return { logs, exitInfo };
|
|
427
423
|
}
|
|
428
424
|
|
|
429
|
-
async function
|
|
425
|
+
async function mcpSmokeTest(
|
|
430
426
|
params: { url: string; probeTimeoutMs: number },
|
|
431
427
|
deps: {
|
|
432
428
|
fetch: typeof fetch;
|
|
@@ -437,6 +433,8 @@ async function waitForMcpReady(
|
|
|
437
433
|
},
|
|
438
434
|
): Promise<void> {
|
|
439
435
|
const deadline = Date.now() + params.probeTimeoutMs;
|
|
436
|
+
const requiredSessions = 2;
|
|
437
|
+
|
|
440
438
|
while (Date.now() < deadline) {
|
|
441
439
|
const exitInfo = deps.getExitInfo();
|
|
442
440
|
if (exitInfo) {
|
|
@@ -446,61 +444,38 @@ async function waitForMcpReady(
|
|
|
446
444
|
}
|
|
447
445
|
|
|
448
446
|
try {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
body: JSON.stringify({
|
|
463
|
-
jsonrpc: "2.0",
|
|
464
|
-
id: LOCAL_TEST_PROBE_ID,
|
|
465
|
-
method: "initialize",
|
|
466
|
-
params: {
|
|
467
|
-
protocolVersion: LOCAL_TEST_PROTOCOL_VERSION,
|
|
468
|
-
capabilities: {},
|
|
469
|
-
clientInfo: {
|
|
470
|
-
name: "MintMCP Local Probe",
|
|
471
|
-
version: "1.0.0",
|
|
472
|
-
},
|
|
447
|
+
for (
|
|
448
|
+
let sessionIndex = 0;
|
|
449
|
+
sessionIndex < requiredSessions;
|
|
450
|
+
sessionIndex++
|
|
451
|
+
) {
|
|
452
|
+
const client = new Client({
|
|
453
|
+
name: "MintMCP Local Probe",
|
|
454
|
+
version: "1.0.0",
|
|
455
|
+
});
|
|
456
|
+
const transport = new StreamableHTTPClientTransport(
|
|
457
|
+
new URL(params.url),
|
|
458
|
+
{
|
|
459
|
+
fetch: deps.fetch,
|
|
473
460
|
},
|
|
474
|
-
|
|
475
|
-
});
|
|
476
|
-
if (response.status !== 200 && response.status !== 202) {
|
|
477
|
-
throw new Error(`Probe returned HTTP ${response.status}`);
|
|
478
|
-
}
|
|
461
|
+
);
|
|
479
462
|
|
|
480
|
-
let responseBody = "";
|
|
481
|
-
if (response.body != undefined) {
|
|
482
|
-
const reader = response.body.getReader();
|
|
483
463
|
try {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
}
|
|
464
|
+
await client.connect(transport as Transport);
|
|
465
|
+
console.log(
|
|
466
|
+
`Local MCP initialize succeeded (${sessionIndex + 1}/${requiredSessions}): ${params.url}`,
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
const toolsResult = await client.listTools();
|
|
470
|
+
console.log(
|
|
471
|
+
`Local tools/list succeeded (${sessionIndex + 1}/${requiredSessions}): ${toolsResult.tools.length} tool(s) found`,
|
|
472
|
+
);
|
|
495
473
|
} finally {
|
|
496
|
-
|
|
474
|
+
await client.close();
|
|
497
475
|
}
|
|
498
476
|
}
|
|
499
477
|
|
|
500
|
-
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
throw new Error("Probe returned an unexpected JSON-RPC payload");
|
|
478
|
+
return;
|
|
504
479
|
} catch {
|
|
505
480
|
const exitInfo = deps.getExitInfo();
|
|
506
481
|
if (exitInfo) {
|
|
@@ -517,43 +492,6 @@ async function waitForMcpReady(
|
|
|
517
492
|
);
|
|
518
493
|
}
|
|
519
494
|
|
|
520
|
-
function isInitializeSuccessResponse(bodyText: string): boolean {
|
|
521
|
-
const parseCandidate = (candidate: string): boolean => {
|
|
522
|
-
try {
|
|
523
|
-
const parsed = JSON.parse(candidate) as {
|
|
524
|
-
jsonrpc?: unknown;
|
|
525
|
-
id?: unknown;
|
|
526
|
-
result?: unknown;
|
|
527
|
-
error?: unknown;
|
|
528
|
-
};
|
|
529
|
-
return (
|
|
530
|
-
parsed.jsonrpc === "2.0" &&
|
|
531
|
-
parsed.id === LOCAL_TEST_PROBE_ID &&
|
|
532
|
-
parsed.error == undefined &&
|
|
533
|
-
typeof parsed.result === "object" &&
|
|
534
|
-
parsed.result != undefined
|
|
535
|
-
);
|
|
536
|
-
} catch {
|
|
537
|
-
return false;
|
|
538
|
-
}
|
|
539
|
-
};
|
|
540
|
-
|
|
541
|
-
const trimmed = bodyText.trim();
|
|
542
|
-
if (trimmed.length === 0) {
|
|
543
|
-
return false;
|
|
544
|
-
}
|
|
545
|
-
if (parseCandidate(trimmed)) {
|
|
546
|
-
return true;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
const lines = trimmed
|
|
550
|
-
.split(/\r?\n/)
|
|
551
|
-
.map((line) => line.trim())
|
|
552
|
-
.filter((line) => line.length > 0)
|
|
553
|
-
.map((line) => (line.startsWith("data:") ? line.slice(5).trim() : line));
|
|
554
|
-
return lines.some((line) => parseCandidate(line));
|
|
555
|
-
}
|
|
556
|
-
|
|
557
495
|
async function stopContainer(
|
|
558
496
|
containerName: string,
|
|
559
497
|
deps: ContainerBuilderDeps,
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import z, { ZodSchema } from "zod";
|
|
|
14
14
|
import {
|
|
15
15
|
type AppRouter,
|
|
16
16
|
type GatewayId,
|
|
17
|
+
GatewayIdSchema,
|
|
17
18
|
HostedIdSchema,
|
|
18
19
|
type HostedTransport,
|
|
19
20
|
UserConfigUpdateSchema,
|
|
@@ -86,6 +87,10 @@ const HOSTED_CONFIG_PATHS: Record<Env, string> = {
|
|
|
86
87
|
const HostedConfigBaseSchema = z.object({
|
|
87
88
|
hostedId: HostedIdSchema,
|
|
88
89
|
|
|
90
|
+
// Gateway ID for this hosted server. Newer config files include this;
|
|
91
|
+
// older ones may omit it. Eventually this becomes the primary identifier.
|
|
92
|
+
gatewayId: GatewayIdSchema.optional(),
|
|
93
|
+
|
|
89
94
|
// Organization that owns this hosted server. Older config files may omit it.
|
|
90
95
|
organizationId: z.string().optional(),
|
|
91
96
|
});
|
|
@@ -112,11 +117,27 @@ type DeployResult = {
|
|
|
112
117
|
|
|
113
118
|
type RegistryPushSession =
|
|
114
119
|
CliClientAppRouterOutputs["mcpHost"]["createRegistryPushSession"];
|
|
120
|
+
type RegistryPushImageRefInput = Pick<
|
|
121
|
+
RegistryPushSession,
|
|
122
|
+
"registryHost" | "repository" | "imageTag"
|
|
123
|
+
>;
|
|
115
124
|
|
|
116
125
|
function storedTransport(config: HostedConfig): Transport | undefined {
|
|
117
126
|
return "mode" in config ? config.transport : undefined;
|
|
118
127
|
}
|
|
119
128
|
|
|
129
|
+
function localTransportFromHostedTransport(
|
|
130
|
+
transport: HostedTransport,
|
|
131
|
+
): Transport {
|
|
132
|
+
return transport.type;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function rejectBuildAndPushTransportChange(transport: Transport): never {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`--transport ${transport} is only supported when creating a new managed connector with build-and-push. To change transport on an existing connector, use deploy or build-and-deploy.`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
120
141
|
function canUseDirectImageStartup(params: {
|
|
121
142
|
transport: Transport | undefined;
|
|
122
143
|
mntDataDir: string | undefined;
|
|
@@ -316,6 +337,7 @@ async function createNew(
|
|
|
316
337
|
return {
|
|
317
338
|
config: HostedConfigSchema.parse({
|
|
318
339
|
hostedId: createdServer.hostedId,
|
|
340
|
+
gatewayId: createdServer.gatewayId,
|
|
319
341
|
...(image != undefined
|
|
320
342
|
? { mode: "container" as const, image, transport }
|
|
321
343
|
: {}),
|
|
@@ -332,6 +354,7 @@ async function updateExisting(
|
|
|
332
354
|
config: HostedConfig,
|
|
333
355
|
organizationId: string,
|
|
334
356
|
options: DeployOptions,
|
|
357
|
+
gatewayId?: GatewayId,
|
|
335
358
|
): Promise<DeployResult> {
|
|
336
359
|
if (
|
|
337
360
|
config.organizationId != undefined &&
|
|
@@ -390,12 +413,18 @@ async function updateExisting(
|
|
|
390
413
|
: undefined;
|
|
391
414
|
const updatedServer = await client.mcpHost.partialUpdateServer.mutate({
|
|
392
415
|
hostedId: config.hostedId,
|
|
416
|
+
...(gatewayId != undefined ? { gatewayId } : {}),
|
|
393
417
|
update: buildPartialUpdate(options, upload?.secretDataZipGcsPath, {
|
|
394
418
|
clearCommand,
|
|
395
419
|
}),
|
|
396
420
|
});
|
|
397
421
|
return {
|
|
398
|
-
config:
|
|
422
|
+
config: HostedConfigSchema.parse({
|
|
423
|
+
...newConfig,
|
|
424
|
+
...(updatedServer.gatewayId != undefined
|
|
425
|
+
? { gatewayId: updatedServer.gatewayId }
|
|
426
|
+
: {}),
|
|
427
|
+
}),
|
|
399
428
|
...(updatedServer.gatewayId != undefined
|
|
400
429
|
? { gatewayId: updatedServer.gatewayId }
|
|
401
430
|
: {}),
|
|
@@ -437,7 +466,7 @@ function updateConfigAfterManagedPush(
|
|
|
437
466
|
});
|
|
438
467
|
}
|
|
439
468
|
|
|
440
|
-
function managedRegistryImageRef(session:
|
|
469
|
+
function managedRegistryImageRef(session: RegistryPushImageRefInput): string {
|
|
441
470
|
return `${session.registryHost}/${session.repository}:${session.imageTag}`;
|
|
442
471
|
}
|
|
443
472
|
|
|
@@ -445,6 +474,7 @@ async function buildAndPushManagedImage(params: {
|
|
|
445
474
|
client: AuthenticatedClient;
|
|
446
475
|
organizationId: string;
|
|
447
476
|
config: HostedConfig;
|
|
477
|
+
gatewayId?: GatewayId;
|
|
448
478
|
dockerfile: string;
|
|
449
479
|
context: string;
|
|
450
480
|
buildArg: string[];
|
|
@@ -462,6 +492,7 @@ async function buildAndPushManagedImage(params: {
|
|
|
462
492
|
|
|
463
493
|
const session = await params.client.mcpHost.createRegistryPushSession.mutate({
|
|
464
494
|
hostedId: params.config.hostedId,
|
|
495
|
+
...(params.gatewayId != undefined ? { gatewayId: params.gatewayId } : {}),
|
|
465
496
|
});
|
|
466
497
|
|
|
467
498
|
const deployImageRef = managedRegistryImageRef(session);
|
|
@@ -479,15 +510,22 @@ async function buildAndPushManagedImage(params: {
|
|
|
479
510
|
|
|
480
511
|
const finalized = await params.client.mcpHost.finalizeRegistryPush.mutate({
|
|
481
512
|
hostedId: params.config.hostedId,
|
|
513
|
+
...(params.gatewayId != undefined ? { gatewayId: params.gatewayId } : {}),
|
|
482
514
|
finalizeToken: session.finalizeToken,
|
|
483
515
|
});
|
|
484
516
|
|
|
517
|
+
const updatedConfig = updateConfigAfterManagedPush(
|
|
518
|
+
params.config,
|
|
519
|
+
params.organizationId,
|
|
520
|
+
deployImageRef,
|
|
521
|
+
);
|
|
485
522
|
return {
|
|
486
|
-
config:
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
523
|
+
config: HostedConfigSchema.parse({
|
|
524
|
+
...updatedConfig,
|
|
525
|
+
...(finalized.gatewayId != undefined
|
|
526
|
+
? { gatewayId: finalized.gatewayId }
|
|
527
|
+
: {}),
|
|
528
|
+
}),
|
|
491
529
|
...(finalized.gatewayId != undefined
|
|
492
530
|
? { gatewayId: finalized.gatewayId }
|
|
493
531
|
: {}),
|
|
@@ -567,6 +605,7 @@ async function buildAndPushManagedImageForCreate(params: {
|
|
|
567
605
|
return {
|
|
568
606
|
config: HostedConfigSchema.parse({
|
|
569
607
|
hostedId: finalized.hostedId,
|
|
608
|
+
gatewayId: finalized.gatewayId,
|
|
570
609
|
organizationId: params.organizationId,
|
|
571
610
|
mode: "container" as const,
|
|
572
611
|
image: deployImageRef,
|
|
@@ -582,24 +621,71 @@ async function deployWithOptions(
|
|
|
582
621
|
env: Env,
|
|
583
622
|
base: string,
|
|
584
623
|
options: DeployOptions,
|
|
624
|
+
gatewayId?: GatewayId,
|
|
585
625
|
): Promise<void> {
|
|
586
626
|
const auth = await getAuth(env);
|
|
587
627
|
|
|
588
628
|
await withAuthRetry(env, async (client) => {
|
|
589
629
|
const configPath = path.join(base, HOSTED_CONFIG_PATHS[env]);
|
|
630
|
+
const hasExistingConfig = fs.existsSync(configPath);
|
|
590
631
|
|
|
591
632
|
let result: DeployResult;
|
|
592
|
-
if (
|
|
593
|
-
console.log("Updating existing server...");
|
|
633
|
+
if (hasExistingConfig) {
|
|
594
634
|
const configData = fs.readFileSync(configPath, "utf8");
|
|
635
|
+
const config = HostedConfigSchema.parse(JSON.parse(configData));
|
|
636
|
+
if (
|
|
637
|
+
gatewayId != undefined &&
|
|
638
|
+
config.gatewayId != undefined &&
|
|
639
|
+
config.gatewayId !== gatewayId
|
|
640
|
+
) {
|
|
641
|
+
throw new Error(
|
|
642
|
+
`--gateway-id ${gatewayId} does not match the gatewayId in ${configPath} (${config.gatewayId}). Remove ${configPath} or omit --gateway-id.`,
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
console.log("Updating existing server...");
|
|
595
646
|
result = await updateExisting(
|
|
596
647
|
client,
|
|
597
648
|
base,
|
|
598
|
-
|
|
649
|
+
config,
|
|
599
650
|
auth.organizationId,
|
|
600
651
|
options,
|
|
652
|
+
gatewayId,
|
|
601
653
|
);
|
|
602
654
|
logDeployResult(env, "Updated", result);
|
|
655
|
+
} else if (gatewayId != undefined) {
|
|
656
|
+
console.log(`Updating existing server via gateway ${gatewayId}...`);
|
|
657
|
+
const upload =
|
|
658
|
+
options.mntDataDir != undefined
|
|
659
|
+
? await uploadData(client, path.join(base, options.mntDataDir))
|
|
660
|
+
: undefined;
|
|
661
|
+
const updatedServer = await client.mcpHost.partialUpdateServer.mutate({
|
|
662
|
+
gatewayId,
|
|
663
|
+
update: buildPartialUpdate(options, upload?.secretDataZipGcsPath),
|
|
664
|
+
});
|
|
665
|
+
const resolvedGatewayId = updatedServer.gatewayId ?? gatewayId;
|
|
666
|
+
const finalImage = updatedServer.config.image;
|
|
667
|
+
const finalTransport = localTransportFromHostedTransport(
|
|
668
|
+
updatedServer.config.transport,
|
|
669
|
+
);
|
|
670
|
+
result = {
|
|
671
|
+
config: HostedConfigSchema.parse({
|
|
672
|
+
hostedId: updatedServer.hostedId,
|
|
673
|
+
gatewayId: resolvedGatewayId,
|
|
674
|
+
organizationId: auth.organizationId,
|
|
675
|
+
...(finalImage != undefined
|
|
676
|
+
? {
|
|
677
|
+
mode: "container" as const,
|
|
678
|
+
image: finalImage,
|
|
679
|
+
transport: finalTransport,
|
|
680
|
+
}
|
|
681
|
+
: {}),
|
|
682
|
+
...(options.mntDataDir != undefined
|
|
683
|
+
? { mntDataDir: options.mntDataDir }
|
|
684
|
+
: {}),
|
|
685
|
+
}),
|
|
686
|
+
gatewayId: resolvedGatewayId,
|
|
687
|
+
};
|
|
688
|
+
logDeployResult(env, "Updated", result);
|
|
603
689
|
} else {
|
|
604
690
|
console.log("Creating new server...");
|
|
605
691
|
result = await createNew(client, base, auth.organizationId, options);
|
|
@@ -750,9 +836,14 @@ program
|
|
|
750
836
|
"--mnt-data-dir <directory>",
|
|
751
837
|
`Directory to copy to /mnt on the server. ${makeDefaultMessage("mntDataDir")}`,
|
|
752
838
|
)
|
|
839
|
+
.option(
|
|
840
|
+
"--gateway-id <id>",
|
|
841
|
+
"Gateway ID of an existing connector to update. Use when no local hosted config exists.",
|
|
842
|
+
argParser(GatewayIdSchema),
|
|
843
|
+
)
|
|
753
844
|
.action(async (options) => {
|
|
754
845
|
const { env, base } = program.opts();
|
|
755
|
-
await deployWithOptions(env, base, options);
|
|
846
|
+
await deployWithOptions(env, base, options, options.gatewayId);
|
|
756
847
|
});
|
|
757
848
|
|
|
758
849
|
program
|
|
@@ -858,6 +949,11 @@ program
|
|
|
858
949
|
"--startup-command <command>",
|
|
859
950
|
"Startup command to configure when creating a new managed connector",
|
|
860
951
|
)
|
|
952
|
+
.option(
|
|
953
|
+
"--gateway-id <id>",
|
|
954
|
+
"Gateway ID of an existing connector to update. Use when no local hosted config exists.",
|
|
955
|
+
argParser(GatewayIdSchema),
|
|
956
|
+
)
|
|
861
957
|
.action(async (options) => {
|
|
862
958
|
const { env, base } = program.opts();
|
|
863
959
|
|
|
@@ -876,13 +972,28 @@ program
|
|
|
876
972
|
|
|
877
973
|
const result = await withAuthRetry(env, async (client) => {
|
|
878
974
|
if (hasExistingConfig) {
|
|
975
|
+
if (options.transport != undefined) {
|
|
976
|
+
rejectBuildAndPushTransportChange(options.transport);
|
|
977
|
+
}
|
|
879
978
|
const config = HostedConfigSchema.parse(
|
|
880
979
|
JSON.parse(fs.readFileSync(configPath, "utf8")),
|
|
881
980
|
);
|
|
981
|
+
if (
|
|
982
|
+
options.gatewayId != undefined &&
|
|
983
|
+
config.gatewayId != undefined &&
|
|
984
|
+
config.gatewayId !== options.gatewayId
|
|
985
|
+
) {
|
|
986
|
+
throw new Error(
|
|
987
|
+
`--gateway-id ${options.gatewayId} does not match the gatewayId in ${configPath} (${config.gatewayId}). Remove ${configPath} or omit --gateway-id.`,
|
|
988
|
+
);
|
|
989
|
+
}
|
|
882
990
|
return await buildAndPushManagedImage({
|
|
883
991
|
client,
|
|
884
992
|
organizationId: auth.organizationId,
|
|
885
993
|
config,
|
|
994
|
+
...(options.gatewayId != undefined
|
|
995
|
+
? { gatewayId: options.gatewayId }
|
|
996
|
+
: {}),
|
|
886
997
|
dockerfile: options.dockerfile,
|
|
887
998
|
context: options.context,
|
|
888
999
|
buildArg: options.buildArg,
|
|
@@ -891,6 +1002,49 @@ program
|
|
|
891
1002
|
});
|
|
892
1003
|
}
|
|
893
1004
|
|
|
1005
|
+
if (options.gatewayId != undefined) {
|
|
1006
|
+
if (options.transport != undefined) {
|
|
1007
|
+
rejectBuildAndPushTransportChange(options.transport);
|
|
1008
|
+
}
|
|
1009
|
+
const gwId = options.gatewayId;
|
|
1010
|
+
const session = await client.mcpHost.createRegistryPushSession.mutate({
|
|
1011
|
+
gatewayId: gwId,
|
|
1012
|
+
});
|
|
1013
|
+
const deployImageRef = managedRegistryImageRef(session);
|
|
1014
|
+
const buildResult = buildImage({
|
|
1015
|
+
dockerfile: options.dockerfile,
|
|
1016
|
+
context: options.context,
|
|
1017
|
+
buildArgs: options.buildArg,
|
|
1018
|
+
...(options.target != undefined ? { target: options.target } : {}),
|
|
1019
|
+
platform: options.platform,
|
|
1020
|
+
imageRef: deployImageRef,
|
|
1021
|
+
});
|
|
1022
|
+
loginToRegistry(
|
|
1023
|
+
session.registryHost,
|
|
1024
|
+
session.username,
|
|
1025
|
+
session.password,
|
|
1026
|
+
);
|
|
1027
|
+
pushImage(buildResult.imageRef);
|
|
1028
|
+
const finalized = await client.mcpHost.finalizeRegistryPush.mutate({
|
|
1029
|
+
gatewayId: gwId,
|
|
1030
|
+
finalizeToken: session.finalizeToken,
|
|
1031
|
+
});
|
|
1032
|
+
const resolvedGatewayId = finalized.gatewayId ?? gwId;
|
|
1033
|
+
return {
|
|
1034
|
+
config: HostedConfigSchema.parse({
|
|
1035
|
+
hostedId: finalized.hostedId,
|
|
1036
|
+
gatewayId: resolvedGatewayId,
|
|
1037
|
+
organizationId: auth.organizationId,
|
|
1038
|
+
mode: "container" as const,
|
|
1039
|
+
image: deployImageRef,
|
|
1040
|
+
transport: localTransportFromHostedTransport(
|
|
1041
|
+
session.currentConfig.transport,
|
|
1042
|
+
),
|
|
1043
|
+
}),
|
|
1044
|
+
gatewayId: resolvedGatewayId,
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
|
|
894
1048
|
return await buildAndPushManagedImageForCreate({
|
|
895
1049
|
client,
|
|
896
1050
|
organizationId: auth.organizationId,
|
|
@@ -954,6 +1108,11 @@ program
|
|
|
954
1108
|
"--mnt-data-dir <directory>",
|
|
955
1109
|
`Directory to copy to /mnt on the server. ${makeDefaultMessage("mntDataDir")}`,
|
|
956
1110
|
)
|
|
1111
|
+
.option(
|
|
1112
|
+
"--gateway-id <id>",
|
|
1113
|
+
"Gateway ID of an existing connector to update. Use when no local hosted config exists.",
|
|
1114
|
+
argParser(GatewayIdSchema),
|
|
1115
|
+
)
|
|
957
1116
|
.action(async (options) => {
|
|
958
1117
|
const { env, base } = program.opts();
|
|
959
1118
|
|
|
@@ -978,19 +1137,24 @@ program
|
|
|
978
1137
|
pushImage(buildResult.imageRef);
|
|
979
1138
|
assertImageSupportsPlatform(buildResult.imageRef, options.platform);
|
|
980
1139
|
|
|
981
|
-
await deployWithOptions(
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
: {}),
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1140
|
+
await deployWithOptions(
|
|
1141
|
+
env,
|
|
1142
|
+
base,
|
|
1143
|
+
{
|
|
1144
|
+
image: buildResult.imageRef,
|
|
1145
|
+
...(options.name != undefined ? { name: options.name } : {}),
|
|
1146
|
+
...(options.transport != undefined
|
|
1147
|
+
? { transport: options.transport }
|
|
1148
|
+
: {}),
|
|
1149
|
+
...(options.startupCommand != undefined
|
|
1150
|
+
? { startupCommand: options.startupCommand }
|
|
1151
|
+
: {}),
|
|
1152
|
+
...(options.mntDataDir != undefined
|
|
1153
|
+
? { mntDataDir: options.mntDataDir }
|
|
1154
|
+
: {}),
|
|
1155
|
+
},
|
|
1156
|
+
options.gatewayId,
|
|
1157
|
+
);
|
|
994
1158
|
});
|
|
995
1159
|
|
|
996
1160
|
program
|
|
@@ -1031,4 +1195,14 @@ program
|
|
|
1031
1195
|
}
|
|
1032
1196
|
});
|
|
1033
1197
|
|
|
1034
|
-
|
|
1198
|
+
function cliErrorMessage(error: unknown): string {
|
|
1199
|
+
if (error instanceof Error) {
|
|
1200
|
+
return error.message;
|
|
1201
|
+
}
|
|
1202
|
+
return String(error);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
void program.parseAsync(process.argv).catch((error: unknown) => {
|
|
1206
|
+
console.error(cliErrorMessage(error));
|
|
1207
|
+
process.exitCode = 1;
|
|
1208
|
+
});
|