@mintmcp/hosted-cli 0.0.1 → 0.0.2
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 +207 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +72 -0
- package/dist/api.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +270 -0
- package/dist/index.js.map +1 -0
- package/package.json +24 -4
- package/src/api.ts +90 -0
- package/src/index.ts +358 -0
- package/tsconfig.json +31 -0
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
export declare const HOSTED_ID_PREFIX: "hosted-";
|
|
3
|
+
export declare const HostedIdSchema: z.ZodBranded<z.ZodEffects<z.ZodString, string, string>, "HostedId">;
|
|
4
|
+
export type HostedId = z.infer<typeof HostedIdSchema>;
|
|
5
|
+
export declare const EnvUpdateValueSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
6
|
+
type: z.ZodLiteral<"copyPreviousValue">;
|
|
7
|
+
fromName: z.ZodString;
|
|
8
|
+
}, "strip", z.ZodTypeAny, {
|
|
9
|
+
type: "copyPreviousValue";
|
|
10
|
+
fromName: string;
|
|
11
|
+
}, {
|
|
12
|
+
type: "copyPreviousValue";
|
|
13
|
+
fromName: string;
|
|
14
|
+
}>, z.ZodObject<{
|
|
15
|
+
type: z.ZodLiteral<"set">;
|
|
16
|
+
value: z.ZodString;
|
|
17
|
+
isSecret: z.ZodBoolean;
|
|
18
|
+
}, "strip", z.ZodTypeAny, {
|
|
19
|
+
value: string;
|
|
20
|
+
type: "set";
|
|
21
|
+
isSecret: boolean;
|
|
22
|
+
}, {
|
|
23
|
+
value: string;
|
|
24
|
+
type: "set";
|
|
25
|
+
isSecret: boolean;
|
|
26
|
+
}>]>;
|
|
27
|
+
export type EnvUpdateValue = z.infer<typeof EnvUpdateValueSchema>;
|
|
28
|
+
export declare const UserConfigUpdateSchema: z.ZodObject<{
|
|
29
|
+
userGivenName: z.ZodString;
|
|
30
|
+
image: z.ZodDefault<z.ZodString>;
|
|
31
|
+
command: z.ZodArray<z.ZodString, "many">;
|
|
32
|
+
cpu: z.ZodDefault<z.ZodNumber>;
|
|
33
|
+
memoryMiB: z.ZodDefault<z.ZodNumber>;
|
|
34
|
+
urlPath: z.ZodDefault<z.ZodString>;
|
|
35
|
+
secretDataZipGcsPath: z.ZodOptional<z.ZodString>;
|
|
36
|
+
startupProbe: z.ZodDefault<z.ZodUnion<[z.ZodLiteral<"startupProbeOn">, z.ZodLiteral<"startupProbeOff">]>>;
|
|
37
|
+
replaceEnv: z.ZodArray<z.ZodObject<{
|
|
38
|
+
name: z.ZodString;
|
|
39
|
+
value: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
40
|
+
type: z.ZodLiteral<"copyPreviousValue">;
|
|
41
|
+
fromName: z.ZodString;
|
|
42
|
+
}, "strip", z.ZodTypeAny, {
|
|
43
|
+
type: "copyPreviousValue";
|
|
44
|
+
fromName: string;
|
|
45
|
+
}, {
|
|
46
|
+
type: "copyPreviousValue";
|
|
47
|
+
fromName: string;
|
|
48
|
+
}>, z.ZodObject<{
|
|
49
|
+
type: z.ZodLiteral<"set">;
|
|
50
|
+
value: z.ZodString;
|
|
51
|
+
isSecret: z.ZodBoolean;
|
|
52
|
+
}, "strip", z.ZodTypeAny, {
|
|
53
|
+
value: string;
|
|
54
|
+
type: "set";
|
|
55
|
+
isSecret: boolean;
|
|
56
|
+
}, {
|
|
57
|
+
value: string;
|
|
58
|
+
type: "set";
|
|
59
|
+
isSecret: boolean;
|
|
60
|
+
}>]>;
|
|
61
|
+
}, "strip", z.ZodTypeAny, {
|
|
62
|
+
value: {
|
|
63
|
+
type: "copyPreviousValue";
|
|
64
|
+
fromName: string;
|
|
65
|
+
} | {
|
|
66
|
+
value: string;
|
|
67
|
+
type: "set";
|
|
68
|
+
isSecret: boolean;
|
|
69
|
+
};
|
|
70
|
+
name: string;
|
|
71
|
+
}, {
|
|
72
|
+
value: {
|
|
73
|
+
type: "copyPreviousValue";
|
|
74
|
+
fromName: string;
|
|
75
|
+
} | {
|
|
76
|
+
value: string;
|
|
77
|
+
type: "set";
|
|
78
|
+
isSecret: boolean;
|
|
79
|
+
};
|
|
80
|
+
name: string;
|
|
81
|
+
}>, "many">;
|
|
82
|
+
}, "strip", z.ZodTypeAny, {
|
|
83
|
+
userGivenName: string;
|
|
84
|
+
image: string;
|
|
85
|
+
command: string[];
|
|
86
|
+
cpu: number;
|
|
87
|
+
memoryMiB: number;
|
|
88
|
+
urlPath: string;
|
|
89
|
+
startupProbe: "startupProbeOn" | "startupProbeOff";
|
|
90
|
+
replaceEnv: {
|
|
91
|
+
value: {
|
|
92
|
+
type: "copyPreviousValue";
|
|
93
|
+
fromName: string;
|
|
94
|
+
} | {
|
|
95
|
+
value: string;
|
|
96
|
+
type: "set";
|
|
97
|
+
isSecret: boolean;
|
|
98
|
+
};
|
|
99
|
+
name: string;
|
|
100
|
+
}[];
|
|
101
|
+
secretDataZipGcsPath?: string | undefined;
|
|
102
|
+
}, {
|
|
103
|
+
userGivenName: string;
|
|
104
|
+
command: string[];
|
|
105
|
+
replaceEnv: {
|
|
106
|
+
value: {
|
|
107
|
+
type: "copyPreviousValue";
|
|
108
|
+
fromName: string;
|
|
109
|
+
} | {
|
|
110
|
+
value: string;
|
|
111
|
+
type: "set";
|
|
112
|
+
isSecret: boolean;
|
|
113
|
+
};
|
|
114
|
+
name: string;
|
|
115
|
+
}[];
|
|
116
|
+
image?: string | undefined;
|
|
117
|
+
cpu?: number | undefined;
|
|
118
|
+
memoryMiB?: number | undefined;
|
|
119
|
+
urlPath?: string | undefined;
|
|
120
|
+
secretDataZipGcsPath?: string | undefined;
|
|
121
|
+
startupProbe?: "startupProbeOn" | "startupProbeOff" | undefined;
|
|
122
|
+
}>;
|
|
123
|
+
declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
124
|
+
ctx: {};
|
|
125
|
+
meta: object;
|
|
126
|
+
errorShape: import("@trpc/server").TRPCDefaultErrorShape;
|
|
127
|
+
transformer: true;
|
|
128
|
+
}, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
|
|
129
|
+
mcpHost: import("@trpc/server").TRPCBuiltRouter<{
|
|
130
|
+
ctx: {};
|
|
131
|
+
meta: object;
|
|
132
|
+
errorShape: import("@trpc/server").TRPCDefaultErrorShape;
|
|
133
|
+
transformer: true;
|
|
134
|
+
}, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
|
|
135
|
+
createServer: import("@trpc/server").TRPCMutationProcedure<{
|
|
136
|
+
input: {
|
|
137
|
+
config: {
|
|
138
|
+
userGivenName: string;
|
|
139
|
+
command: string[];
|
|
140
|
+
replaceEnv: {
|
|
141
|
+
value: {
|
|
142
|
+
type: "copyPreviousValue";
|
|
143
|
+
fromName: string;
|
|
144
|
+
} | {
|
|
145
|
+
value: string;
|
|
146
|
+
type: "set";
|
|
147
|
+
isSecret: boolean;
|
|
148
|
+
};
|
|
149
|
+
name: string;
|
|
150
|
+
}[];
|
|
151
|
+
image?: string | undefined;
|
|
152
|
+
cpu?: number | undefined;
|
|
153
|
+
memoryMiB?: number | undefined;
|
|
154
|
+
urlPath?: string | undefined;
|
|
155
|
+
secretDataZipGcsPath?: string | undefined;
|
|
156
|
+
startupProbe?: "startupProbeOn" | "startupProbeOff" | undefined;
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
output: {
|
|
160
|
+
hostedId: string & z.BRAND<"HostedId">;
|
|
161
|
+
};
|
|
162
|
+
meta: object;
|
|
163
|
+
}>;
|
|
164
|
+
updateServer: import("@trpc/server").TRPCMutationProcedure<{
|
|
165
|
+
input: {
|
|
166
|
+
config: {
|
|
167
|
+
userGivenName: string;
|
|
168
|
+
command: string[];
|
|
169
|
+
replaceEnv: {
|
|
170
|
+
value: {
|
|
171
|
+
type: "copyPreviousValue";
|
|
172
|
+
fromName: string;
|
|
173
|
+
} | {
|
|
174
|
+
value: string;
|
|
175
|
+
type: "set";
|
|
176
|
+
isSecret: boolean;
|
|
177
|
+
};
|
|
178
|
+
name: string;
|
|
179
|
+
}[];
|
|
180
|
+
image?: string | undefined;
|
|
181
|
+
cpu?: number | undefined;
|
|
182
|
+
memoryMiB?: number | undefined;
|
|
183
|
+
urlPath?: string | undefined;
|
|
184
|
+
secretDataZipGcsPath?: string | undefined;
|
|
185
|
+
startupProbe?: "startupProbeOn" | "startupProbeOff" | undefined;
|
|
186
|
+
};
|
|
187
|
+
app: string;
|
|
188
|
+
};
|
|
189
|
+
output: void;
|
|
190
|
+
meta: object;
|
|
191
|
+
}>;
|
|
192
|
+
makeSignedUploadUrl: import("@trpc/server").TRPCMutationProcedure<{
|
|
193
|
+
input: {
|
|
194
|
+
filename: string;
|
|
195
|
+
};
|
|
196
|
+
output: {
|
|
197
|
+
secretDataZipGcsPath: string;
|
|
198
|
+
url: string;
|
|
199
|
+
fields: Record<string, string>;
|
|
200
|
+
};
|
|
201
|
+
meta: object;
|
|
202
|
+
}>;
|
|
203
|
+
}>>;
|
|
204
|
+
}>>;
|
|
205
|
+
export type AppRouter = typeof appRouter;
|
|
206
|
+
export {};
|
|
207
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAQA,OAAO,CAAC,MAAM,KAAK,CAAC;AAKpB,eAAO,MAAM,gBAAgB,EAAG,SAAkB,CAAC;AACnD,eAAO,MAAM,cAAc,qEAOL,CAAC;AACvB,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;IAU/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAajC,CAAC;AAKH,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCb,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC"}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// A stub implementation of the server interface, because tRPC needs the router type to generate its
|
|
2
|
+
// client, and the easiest way to get a router type is to have a server implementation.
|
|
3
|
+
//
|
|
4
|
+
// TODO: Figure out if there is a way to generate the interface so that we don't need to keep them
|
|
5
|
+
// in sync manually.
|
|
6
|
+
import { initTRPC } from "@trpc/server";
|
|
7
|
+
import superjson from "superjson";
|
|
8
|
+
import z from "zod";
|
|
9
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10
|
+
export const HOSTED_ID_PREFIX = "hosted-";
|
|
11
|
+
export const HostedIdSchema = z
|
|
12
|
+
.string()
|
|
13
|
+
.refine((v) => v.startsWith(HOSTED_ID_PREFIX) &&
|
|
14
|
+
UUID_RE.test(v.substring(HOSTED_ID_PREFIX.length)))
|
|
15
|
+
.brand();
|
|
16
|
+
export const EnvUpdateValueSchema = z.discriminatedUnion("type", [
|
|
17
|
+
z.object({
|
|
18
|
+
type: z.literal("copyPreviousValue"),
|
|
19
|
+
fromName: z.string(),
|
|
20
|
+
}),
|
|
21
|
+
z.object({
|
|
22
|
+
type: z.literal("set"),
|
|
23
|
+
value: z.string(),
|
|
24
|
+
isSecret: z.boolean(),
|
|
25
|
+
}),
|
|
26
|
+
]);
|
|
27
|
+
export const UserConfigUpdateSchema = z.object({
|
|
28
|
+
userGivenName: z.string().min(1),
|
|
29
|
+
image: z.string().default("nikolaik/python-nodejs@sha256:d481c61b10a131cb0ae3dfc09adf3beaff76557597010914df48ff1fe1dacbf1"),
|
|
30
|
+
command: z.array(z.string()),
|
|
31
|
+
cpu: z.number().default(1),
|
|
32
|
+
memoryMiB: z.number().default(4096),
|
|
33
|
+
urlPath: z.string().default("/mcp"),
|
|
34
|
+
secretDataZipGcsPath: z.string().optional(),
|
|
35
|
+
startupProbe: z
|
|
36
|
+
.union([z.literal("startupProbeOn"), z.literal("startupProbeOff")])
|
|
37
|
+
.default("startupProbeOn"),
|
|
38
|
+
replaceEnv: z
|
|
39
|
+
.array(z.object({ name: z.string().min(1), value: EnvUpdateValueSchema })),
|
|
40
|
+
});
|
|
41
|
+
const t = initTRPC.context().create({ transformer: superjson });
|
|
42
|
+
const { router, procedure } = t;
|
|
43
|
+
const appRouter = router({
|
|
44
|
+
mcpHost: router({
|
|
45
|
+
createServer: procedure
|
|
46
|
+
.input(z.object({ config: UserConfigUpdateSchema }))
|
|
47
|
+
.output(z.object({ hostedId: HostedIdSchema }))
|
|
48
|
+
.mutation(() => {
|
|
49
|
+
throw new Error("stub");
|
|
50
|
+
}),
|
|
51
|
+
updateServer: procedure
|
|
52
|
+
.input(z.object({
|
|
53
|
+
app: z.string().min(1),
|
|
54
|
+
config: UserConfigUpdateSchema,
|
|
55
|
+
}))
|
|
56
|
+
.output(z.void())
|
|
57
|
+
.mutation(() => {
|
|
58
|
+
throw new Error("stub");
|
|
59
|
+
}),
|
|
60
|
+
makeSignedUploadUrl: procedure
|
|
61
|
+
.input(z.object({ filename: z.string() }))
|
|
62
|
+
.output(z.object({
|
|
63
|
+
url: z.string(),
|
|
64
|
+
fields: z.record(z.string(), z.string()),
|
|
65
|
+
secretDataZipGcsPath: z.string(),
|
|
66
|
+
}))
|
|
67
|
+
.mutation(() => {
|
|
68
|
+
throw new Error("stub");
|
|
69
|
+
}),
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
72
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
|
|
3
|
+
import { type AppRouter } from "./api.js";
|
|
4
|
+
export type CliClientAppRouterInputs = inferRouterInputs<AppRouter>;
|
|
5
|
+
export type CliClientAppRouterOutputs = inferRouterOutputs<AppRouter>;
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAO1E,OAAO,EAA0C,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAElF,MAAM,MAAM,wBAAwB,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;AACpE,MAAM,MAAM,yBAAyB,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command, InvalidArgumentError } from "@commander-js/extra-typings";
|
|
3
|
+
import { createTRPCClient, httpLink } from "@trpc/client";
|
|
4
|
+
import archiver from "archiver";
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import keytar from "keytar";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import superjson from "superjson";
|
|
9
|
+
import z, { ZodSchema } from "zod";
|
|
10
|
+
import { HostedIdSchema, UserConfigUpdateSchema } from "./api.js";
|
|
11
|
+
const EnvSchema = z.enum(["staging", "production"]);
|
|
12
|
+
const TRPC_API_URL = {
|
|
13
|
+
staging: "http://localhost:3000/api.trpc",
|
|
14
|
+
production: "https://api.mintmcp.com/api.trpc",
|
|
15
|
+
};
|
|
16
|
+
const WEB_CLIENT_URL = {
|
|
17
|
+
staging: "http://localhost:3000",
|
|
18
|
+
production: "https://app.mintmcp.com",
|
|
19
|
+
};
|
|
20
|
+
const CLIENT_IDS = {
|
|
21
|
+
staging: "client_01K0WB8ABW72JSBZ62V98AZMPS",
|
|
22
|
+
production: "client_01K0WB8AHKS2J39SFKAPTHTR90",
|
|
23
|
+
};
|
|
24
|
+
// Where to look for the hosted config, relative to "--base".
|
|
25
|
+
const HOSTED_CONFIG_PATHS = {
|
|
26
|
+
staging: ".mintmcp/hosted-staging.json",
|
|
27
|
+
production: ".mintmcp/hosted.json",
|
|
28
|
+
};
|
|
29
|
+
const HostedConfigSchema = z.object({
|
|
30
|
+
hostedId: HostedIdSchema,
|
|
31
|
+
config: UserConfigUpdateSchema.omit({ secretDataZipGcsPath: true }),
|
|
32
|
+
// Directory containing data to send to hosted server, relative to "--base".
|
|
33
|
+
mntDataDir: z.string(),
|
|
34
|
+
});
|
|
35
|
+
const AuthSchema = z.object({
|
|
36
|
+
accessToken: z.string(),
|
|
37
|
+
email: z.string(),
|
|
38
|
+
organizationId: z.string(),
|
|
39
|
+
});
|
|
40
|
+
async function requestAuth(env) {
|
|
41
|
+
const clientId = CLIENT_IDS[env];
|
|
42
|
+
const deviceCodeResponse = await fetch("https://api.workos.com/user_management/authorize/device", {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: {
|
|
45
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
46
|
+
},
|
|
47
|
+
body: new URLSearchParams({
|
|
48
|
+
client_id: clientId,
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
if (!deviceCodeResponse.ok) {
|
|
52
|
+
throw new Error(`Failed to get device code: ${await deviceCodeResponse.text()}`);
|
|
53
|
+
}
|
|
54
|
+
const deviceData = await deviceCodeResponse.json();
|
|
55
|
+
const { device_code: deviceCode, verification_uri_complete: verificationUriComplete, interval = 5, } = deviceData;
|
|
56
|
+
console.log(`To authenticate, visit ${verificationUriComplete}`);
|
|
57
|
+
// Poll for access token.
|
|
58
|
+
const maxAttempts = 60;
|
|
59
|
+
for (let attempts = 0; attempts < maxAttempts; attempts += 1) {
|
|
60
|
+
await new Promise((resolve) => {
|
|
61
|
+
setTimeout(resolve, interval * 1000);
|
|
62
|
+
});
|
|
63
|
+
const tokenResponse = await fetch("https://api.workos.com/user_management/authenticate", {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: {
|
|
66
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
67
|
+
},
|
|
68
|
+
body: new URLSearchParams({
|
|
69
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
70
|
+
device_code: deviceCode,
|
|
71
|
+
client_id: clientId,
|
|
72
|
+
}),
|
|
73
|
+
});
|
|
74
|
+
if (!tokenResponse.ok) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const tokenData = await tokenResponse.json();
|
|
78
|
+
if (tokenData.error) {
|
|
79
|
+
if (tokenData.error === "authorization_pending") {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
else if (tokenData.error === "slow_down") {
|
|
83
|
+
// Increase interval as requested
|
|
84
|
+
await new Promise((resolve) => {
|
|
85
|
+
setTimeout(resolve, 5000);
|
|
86
|
+
});
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
throw new Error(`Authentication failed: ${tokenData.error}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (tokenData.access_token) {
|
|
94
|
+
if (!tokenData.user?.email) {
|
|
95
|
+
throw new Error("Authentication response missing user email");
|
|
96
|
+
}
|
|
97
|
+
if (!tokenData.organization_id) {
|
|
98
|
+
throw new Error("Authentication response missing organization ID");
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
accessToken: tokenData.access_token,
|
|
102
|
+
email: tokenData.user.email,
|
|
103
|
+
organizationId: tokenData.organization_id,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
throw new Error("Authentication timeout");
|
|
108
|
+
}
|
|
109
|
+
async function getAuth(env) {
|
|
110
|
+
const keychainServiceName = "mintmcp-credentials";
|
|
111
|
+
const stored = await keytar.getPassword(keychainServiceName, env);
|
|
112
|
+
if (stored) {
|
|
113
|
+
try {
|
|
114
|
+
const parsedData = JSON.parse(stored);
|
|
115
|
+
const validatedAuth = AuthSchema.parse(parsedData);
|
|
116
|
+
return validatedAuth;
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
console.warn("Invalid stored credentials, re-authenticating:", error);
|
|
120
|
+
// If validation fails, delete the invalid stored credentials and re-authenticate
|
|
121
|
+
await keytar.deletePassword(keychainServiceName, env);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const auth = await requestAuth(env);
|
|
125
|
+
await keytar.setPassword(keychainServiceName, env, JSON.stringify(auth));
|
|
126
|
+
return auth;
|
|
127
|
+
}
|
|
128
|
+
async function makeAuthenticatedClient(env) {
|
|
129
|
+
const auth = await getAuth(env);
|
|
130
|
+
// TODO: Get the organization name because that is more human readable.
|
|
131
|
+
console.log(`Authenticated as ${auth.email} in ${auth.organizationId}.`);
|
|
132
|
+
return createTRPCClient({
|
|
133
|
+
links: [
|
|
134
|
+
httpLink({
|
|
135
|
+
url: TRPC_API_URL[env],
|
|
136
|
+
transformer: superjson,
|
|
137
|
+
headers: {
|
|
138
|
+
Authorization: `Bearer ${auth.accessToken}`,
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
141
|
+
],
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function argParser(schema) {
|
|
145
|
+
return (value) => {
|
|
146
|
+
const result = schema.safeParse(value);
|
|
147
|
+
if (result.success) {
|
|
148
|
+
return result.data;
|
|
149
|
+
}
|
|
150
|
+
throw new InvalidArgumentError(result.error.issues.map((iss) => iss.message).join("\n"));
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
async function createZipBuffer(mntDataDir) {
|
|
154
|
+
return await new Promise((resolve, reject) => {
|
|
155
|
+
const buffers = [];
|
|
156
|
+
const archive = archiver("zip", {
|
|
157
|
+
zlib: { level: 9 },
|
|
158
|
+
});
|
|
159
|
+
archive.on("data", (chunk) => {
|
|
160
|
+
buffers.push(chunk);
|
|
161
|
+
});
|
|
162
|
+
archive.on("end", () => {
|
|
163
|
+
const zipBuffer = Buffer.concat(buffers);
|
|
164
|
+
console.log(`Created zip archive (${zipBuffer.length} total bytes)`);
|
|
165
|
+
resolve(zipBuffer);
|
|
166
|
+
});
|
|
167
|
+
archive.on("error", (err) => {
|
|
168
|
+
reject(err);
|
|
169
|
+
});
|
|
170
|
+
if (fs.existsSync(mntDataDir) && fs.statSync(mntDataDir).isDirectory()) {
|
|
171
|
+
archive.directory(mntDataDir, false);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
reject(new Error(`Directory ${mntDataDir} does not exist or is not a directory`));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
archive.finalize();
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
async function uploadData(client, mntDataDir) {
|
|
181
|
+
console.log("Zipping data...");
|
|
182
|
+
const zipBuffer = await createZipBuffer(mntDataDir);
|
|
183
|
+
console.log("Getting upload URL...");
|
|
184
|
+
const upload = await client.mcpHost.makeSignedUploadUrl.mutate({
|
|
185
|
+
filename: "data.zip",
|
|
186
|
+
});
|
|
187
|
+
console.log("Uploading data...");
|
|
188
|
+
const formData = new FormData();
|
|
189
|
+
for (const [key, value] of Object.entries(upload.fields)) {
|
|
190
|
+
formData.append(key, value);
|
|
191
|
+
}
|
|
192
|
+
const blob = new Blob([new Uint8Array(zipBuffer)], {
|
|
193
|
+
type: "application/zip",
|
|
194
|
+
});
|
|
195
|
+
formData.append("file", blob);
|
|
196
|
+
const uploadResponse = await fetch(upload.url, {
|
|
197
|
+
method: "POST",
|
|
198
|
+
body: formData,
|
|
199
|
+
});
|
|
200
|
+
if (!uploadResponse.ok) {
|
|
201
|
+
throw new Error(`Upload failed: ${uploadResponse.status} ${uploadResponse.statusText}`);
|
|
202
|
+
}
|
|
203
|
+
console.log("File uploaded.");
|
|
204
|
+
return { secretDataZipGcsPath: upload.secretDataZipGcsPath };
|
|
205
|
+
}
|
|
206
|
+
const program = new Command()
|
|
207
|
+
.name("hosted")
|
|
208
|
+
.description("Control hosted servers on MintMCP")
|
|
209
|
+
.version("0.0.1")
|
|
210
|
+
.option("-e, --env <environment>", "target environment", argParser(EnvSchema), "production")
|
|
211
|
+
.requiredOption("-b, --base <base>", "base directory for config and data");
|
|
212
|
+
program
|
|
213
|
+
.command("create")
|
|
214
|
+
.description("Create a hosted server.")
|
|
215
|
+
.requiredOption("--name <name>", "Name of the hosted server")
|
|
216
|
+
.option("--startup-command <command>", "Command that runs on hosted container to start the server (runs in bash).", "cd /mnt && npm run build && npm run start")
|
|
217
|
+
.option("--mnt-data-dir <directory>", "Directory to copy to /mnt on the server.", ".")
|
|
218
|
+
.action(async (options) => {
|
|
219
|
+
const { name, startupCommand, mntDataDir } = options;
|
|
220
|
+
const { env, base } = program.opts();
|
|
221
|
+
const configPath = path.join(base, HOSTED_CONFIG_PATHS[env]);
|
|
222
|
+
if (fs.existsSync(configPath)) {
|
|
223
|
+
throw new Error("Hosted server config already exists.");
|
|
224
|
+
}
|
|
225
|
+
const client = await makeAuthenticatedClient(env);
|
|
226
|
+
const upload = await uploadData(client, path.join(base, mntDataDir));
|
|
227
|
+
const ConfigWithoutPathSchema = UserConfigUpdateSchema.omit({ secretDataZipGcsPath: true });
|
|
228
|
+
const config = ConfigWithoutPathSchema.parse({
|
|
229
|
+
userGivenName: name,
|
|
230
|
+
command: ["bash", "-c", startupCommand],
|
|
231
|
+
replaceEnv: [],
|
|
232
|
+
});
|
|
233
|
+
console.log("Creating server...");
|
|
234
|
+
const createdServer = await client.mcpHost.createServer.mutate({
|
|
235
|
+
config: { ...config, secretDataZipGcsPath: upload.secretDataZipGcsPath },
|
|
236
|
+
});
|
|
237
|
+
const hostedConfig = {
|
|
238
|
+
hostedId: createdServer.hostedId,
|
|
239
|
+
config,
|
|
240
|
+
mntDataDir,
|
|
241
|
+
};
|
|
242
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
243
|
+
fs.writeFileSync(configPath, JSON.stringify(hostedConfig, null, 2));
|
|
244
|
+
console.log(`Created ${WEB_CLIENT_URL[env]}/hosted/${createdServer.hostedId}`);
|
|
245
|
+
});
|
|
246
|
+
program
|
|
247
|
+
.command("update")
|
|
248
|
+
.description("Update a hosted server.")
|
|
249
|
+
.action(async () => {
|
|
250
|
+
const { env, base } = program.opts();
|
|
251
|
+
const configPath = path.join(base, HOSTED_CONFIG_PATHS[env]);
|
|
252
|
+
if (!fs.existsSync(configPath)) {
|
|
253
|
+
throw new Error("Hosted server config not found. Run 'create' command first.");
|
|
254
|
+
}
|
|
255
|
+
const configData = fs.readFileSync(configPath, "utf8");
|
|
256
|
+
const hostedConfig = HostedConfigSchema.parse(JSON.parse(configData));
|
|
257
|
+
const client = await makeAuthenticatedClient(env);
|
|
258
|
+
const upload = await uploadData(client, path.join(base, hostedConfig.mntDataDir));
|
|
259
|
+
console.log("Updating server...");
|
|
260
|
+
await client.mcpHost.updateServer.mutate({
|
|
261
|
+
app: hostedConfig.hostedId,
|
|
262
|
+
config: {
|
|
263
|
+
...hostedConfig.config,
|
|
264
|
+
secretDataZipGcsPath: upload.secretDataZipGcsPath,
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
console.log(`Updated ${WEB_CLIENT_URL[env]}/hosted/${hostedConfig.hostedId}`);
|
|
268
|
+
});
|
|
269
|
+
program.parse();
|
|
270
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mintmcp/hosted-cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Control hosted servers on MintMCP.",
|
|
5
|
-
"main": "index.
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"scripts": {
|
|
7
|
-
"
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prepublishOnly": "npm run build"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"hosted-cli": "dist/index.js"
|
|
8
13
|
},
|
|
9
14
|
"author": "",
|
|
10
|
-
"license": "MIT"
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/archiver": "^6.0.3",
|
|
18
|
+
"@types/node": "^24.3.1",
|
|
19
|
+
"typescript": "^5.9.2"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@commander-js/extra-typings": "^14.0.0",
|
|
23
|
+
"@trpc/client": "^11.4.3",
|
|
24
|
+
"@trpc/server": "^11.4.3",
|
|
25
|
+
"archiver": "^7.0.1",
|
|
26
|
+
"commander": "^14.0.1",
|
|
27
|
+
"keytar": "^7.9.0",
|
|
28
|
+
"superjson": "^2.2.2",
|
|
29
|
+
"zod": "^3.25.76"
|
|
30
|
+
}
|
|
11
31
|
}
|
package/src/api.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// A stub implementation of the server interface, because tRPC needs the router type to generate its
|
|
2
|
+
// client, and the easiest way to get a router type is to have a server implementation.
|
|
3
|
+
//
|
|
4
|
+
// TODO: Figure out if there is a way to generate the interface so that we don't need to keep them
|
|
5
|
+
// in sync manually.
|
|
6
|
+
|
|
7
|
+
import { initTRPC } from "@trpc/server";
|
|
8
|
+
import superjson from "superjson";
|
|
9
|
+
import z from "zod";
|
|
10
|
+
|
|
11
|
+
const UUID_RE =
|
|
12
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
13
|
+
|
|
14
|
+
export const HOSTED_ID_PREFIX = "hosted-" as const;
|
|
15
|
+
export const HostedIdSchema = z
|
|
16
|
+
.string()
|
|
17
|
+
.refine(
|
|
18
|
+
(v) =>
|
|
19
|
+
v.startsWith(HOSTED_ID_PREFIX) &&
|
|
20
|
+
UUID_RE.test(v.substring(HOSTED_ID_PREFIX.length)),
|
|
21
|
+
)
|
|
22
|
+
.brand<"HostedId">();
|
|
23
|
+
export type HostedId = z.infer<typeof HostedIdSchema>;
|
|
24
|
+
|
|
25
|
+
export const EnvUpdateValueSchema = z.discriminatedUnion("type", [
|
|
26
|
+
z.object({
|
|
27
|
+
type: z.literal("copyPreviousValue"),
|
|
28
|
+
fromName: z.string(),
|
|
29
|
+
}),
|
|
30
|
+
z.object({
|
|
31
|
+
type: z.literal("set"),
|
|
32
|
+
value: z.string(),
|
|
33
|
+
isSecret: z.boolean(),
|
|
34
|
+
}),
|
|
35
|
+
]);
|
|
36
|
+
export type EnvUpdateValue = z.infer<typeof EnvUpdateValueSchema>;
|
|
37
|
+
|
|
38
|
+
export const UserConfigUpdateSchema = z.object({
|
|
39
|
+
userGivenName: z.string().min(1),
|
|
40
|
+
image: z.string().default("nikolaik/python-nodejs@sha256:d481c61b10a131cb0ae3dfc09adf3beaff76557597010914df48ff1fe1dacbf1"),
|
|
41
|
+
command: z.array(z.string()),
|
|
42
|
+
cpu: z.number().default(1),
|
|
43
|
+
memoryMiB: z.number().default(4096),
|
|
44
|
+
urlPath: z.string().default("/mcp"),
|
|
45
|
+
secretDataZipGcsPath: z.string().optional(),
|
|
46
|
+
startupProbe: z
|
|
47
|
+
.union([z.literal("startupProbeOn"), z.literal("startupProbeOff")])
|
|
48
|
+
.default("startupProbeOn"),
|
|
49
|
+
replaceEnv: z
|
|
50
|
+
.array(z.object({ name: z.string().min(1), value: EnvUpdateValueSchema })),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const t = initTRPC.context<{}>().create({ transformer: superjson });
|
|
54
|
+
const { router, procedure } = t;
|
|
55
|
+
|
|
56
|
+
const appRouter = router({
|
|
57
|
+
mcpHost: router({
|
|
58
|
+
createServer: procedure
|
|
59
|
+
.input(z.object({ config: UserConfigUpdateSchema }))
|
|
60
|
+
.output(z.object({ hostedId: HostedIdSchema }))
|
|
61
|
+
.mutation(() => {
|
|
62
|
+
throw new Error("stub");
|
|
63
|
+
}),
|
|
64
|
+
updateServer: procedure
|
|
65
|
+
.input(
|
|
66
|
+
z.object({
|
|
67
|
+
app: z.string().min(1),
|
|
68
|
+
config: UserConfigUpdateSchema,
|
|
69
|
+
}),
|
|
70
|
+
)
|
|
71
|
+
.output(z.void())
|
|
72
|
+
.mutation(() => {
|
|
73
|
+
throw new Error("stub");
|
|
74
|
+
}),
|
|
75
|
+
makeSignedUploadUrl: procedure
|
|
76
|
+
.input(z.object({ filename: z.string() }))
|
|
77
|
+
.output(
|
|
78
|
+
z.object({
|
|
79
|
+
url: z.string(),
|
|
80
|
+
fields: z.record(z.string(), z.string()),
|
|
81
|
+
secretDataZipGcsPath: z.string(),
|
|
82
|
+
}),
|
|
83
|
+
)
|
|
84
|
+
.mutation(() => {
|
|
85
|
+
throw new Error("stub");
|
|
86
|
+
}),
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export type AppRouter = typeof appRouter;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command, InvalidArgumentError } from "@commander-js/extra-typings";
|
|
4
|
+
import { createTRPCClient, httpLink } from "@trpc/client";
|
|
5
|
+
import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
|
|
6
|
+
import archiver from "archiver";
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import keytar from "keytar";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
import superjson from "superjson";
|
|
11
|
+
import z, { ZodSchema } from "zod";
|
|
12
|
+
import { HostedIdSchema, UserConfigUpdateSchema, type AppRouter } from "./api.js";
|
|
13
|
+
|
|
14
|
+
export type CliClientAppRouterInputs = inferRouterInputs<AppRouter>;
|
|
15
|
+
export type CliClientAppRouterOutputs = inferRouterOutputs<AppRouter>;
|
|
16
|
+
|
|
17
|
+
const EnvSchema = z.enum(["staging", "production"]);
|
|
18
|
+
type Env = z.infer<typeof EnvSchema>;
|
|
19
|
+
|
|
20
|
+
const TRPC_API_URL: Record<Env, string> = {
|
|
21
|
+
staging: "http://localhost:3000/api.trpc",
|
|
22
|
+
production: "https://api.mintmcp.com/api.trpc",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const WEB_CLIENT_URL: Record<Env, string> = {
|
|
26
|
+
staging: "http://localhost:3000",
|
|
27
|
+
production: "https://app.mintmcp.com",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const CLIENT_IDS: Record<Env, string> = {
|
|
31
|
+
staging: "client_01K0WB8ABW72JSBZ62V98AZMPS",
|
|
32
|
+
production: "client_01K0WB8AHKS2J39SFKAPTHTR90",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Where to look for the hosted config, relative to "--base".
|
|
36
|
+
const HOSTED_CONFIG_PATHS: Record<Env, string> = {
|
|
37
|
+
staging: ".mintmcp/hosted-staging.json",
|
|
38
|
+
production: ".mintmcp/hosted.json",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const HostedConfigSchema = z.object({
|
|
42
|
+
hostedId: HostedIdSchema,
|
|
43
|
+
|
|
44
|
+
config: UserConfigUpdateSchema.omit({ secretDataZipGcsPath: true }),
|
|
45
|
+
|
|
46
|
+
// Directory containing data to send to hosted server, relative to "--base".
|
|
47
|
+
mntDataDir: z.string(),
|
|
48
|
+
});
|
|
49
|
+
type HostedConfig = z.infer<typeof HostedConfigSchema>;
|
|
50
|
+
|
|
51
|
+
const AuthSchema = z.object({
|
|
52
|
+
accessToken: z.string(),
|
|
53
|
+
email: z.string(),
|
|
54
|
+
organizationId: z.string(),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
type Auth = z.infer<typeof AuthSchema>;
|
|
58
|
+
|
|
59
|
+
async function requestAuth(env: Env): Promise<Auth> {
|
|
60
|
+
const clientId = CLIENT_IDS[env];
|
|
61
|
+
|
|
62
|
+
const deviceCodeResponse = await fetch(
|
|
63
|
+
"https://api.workos.com/user_management/authorize/device",
|
|
64
|
+
{
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: {
|
|
67
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
68
|
+
},
|
|
69
|
+
body: new URLSearchParams({
|
|
70
|
+
client_id: clientId,
|
|
71
|
+
}),
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
if (!deviceCodeResponse.ok) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Failed to get device code: ${await deviceCodeResponse.text()}`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const deviceData = await deviceCodeResponse.json() as any;
|
|
82
|
+
const {
|
|
83
|
+
device_code: deviceCode,
|
|
84
|
+
verification_uri_complete: verificationUriComplete,
|
|
85
|
+
interval = 5,
|
|
86
|
+
} = deviceData;
|
|
87
|
+
|
|
88
|
+
console.log(`To authenticate, visit ${verificationUriComplete}`);
|
|
89
|
+
|
|
90
|
+
// Poll for access token.
|
|
91
|
+
const maxAttempts = 60;
|
|
92
|
+
for (let attempts = 0; attempts < maxAttempts; attempts += 1) {
|
|
93
|
+
await new Promise<void>((resolve) => {
|
|
94
|
+
setTimeout(resolve, interval * 1000);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const tokenResponse = await fetch(
|
|
98
|
+
"https://api.workos.com/user_management/authenticate",
|
|
99
|
+
{
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: {
|
|
102
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
103
|
+
},
|
|
104
|
+
body: new URLSearchParams({
|
|
105
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
106
|
+
device_code: deviceCode,
|
|
107
|
+
client_id: clientId,
|
|
108
|
+
}),
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (!tokenResponse.ok) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const tokenData = await tokenResponse.json() as any;
|
|
117
|
+
|
|
118
|
+
if (tokenData.error) {
|
|
119
|
+
if (tokenData.error === "authorization_pending") {
|
|
120
|
+
continue;
|
|
121
|
+
} else if (tokenData.error === "slow_down") {
|
|
122
|
+
// Increase interval as requested
|
|
123
|
+
await new Promise<void>((resolve) => {
|
|
124
|
+
setTimeout(resolve, 5000);
|
|
125
|
+
});
|
|
126
|
+
continue;
|
|
127
|
+
} else {
|
|
128
|
+
throw new Error(`Authentication failed: ${tokenData.error}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (tokenData.access_token) {
|
|
133
|
+
if (!tokenData.user?.email) {
|
|
134
|
+
throw new Error("Authentication response missing user email");
|
|
135
|
+
}
|
|
136
|
+
if (!tokenData.organization_id) {
|
|
137
|
+
throw new Error("Authentication response missing organization ID");
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
accessToken: tokenData.access_token,
|
|
141
|
+
email: tokenData.user.email,
|
|
142
|
+
organizationId: tokenData.organization_id,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
throw new Error("Authentication timeout");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function getAuth(env: Env): Promise<Auth> {
|
|
151
|
+
const keychainServiceName = "mintmcp-credentials";
|
|
152
|
+
const stored = await keytar.getPassword(keychainServiceName, env);
|
|
153
|
+
if (stored) {
|
|
154
|
+
try {
|
|
155
|
+
const parsedData = JSON.parse(stored);
|
|
156
|
+
const validatedAuth = AuthSchema.parse(parsedData);
|
|
157
|
+
return validatedAuth;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.warn("Invalid stored credentials, re-authenticating:", error);
|
|
160
|
+
// If validation fails, delete the invalid stored credentials and re-authenticate
|
|
161
|
+
await keytar.deletePassword(keychainServiceName, env);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const auth = await requestAuth(env);
|
|
165
|
+
await keytar.setPassword(keychainServiceName, env, JSON.stringify(auth));
|
|
166
|
+
return auth;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function makeAuthenticatedClient(env: Env) {
|
|
170
|
+
const auth = await getAuth(env);
|
|
171
|
+
|
|
172
|
+
// TODO: Get the organization name because that is more human readable.
|
|
173
|
+
console.log(`Authenticated as ${auth.email} in ${auth.organizationId}.`);
|
|
174
|
+
|
|
175
|
+
return createTRPCClient<AppRouter>({
|
|
176
|
+
links: [
|
|
177
|
+
httpLink({
|
|
178
|
+
url: TRPC_API_URL[env],
|
|
179
|
+
transformer: superjson,
|
|
180
|
+
headers: {
|
|
181
|
+
Authorization: `Bearer ${auth.accessToken}`,
|
|
182
|
+
},
|
|
183
|
+
}),
|
|
184
|
+
],
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function argParser<T>(schema: ZodSchema<T>) {
|
|
189
|
+
return (value: string): T => {
|
|
190
|
+
const result = schema.safeParse(value);
|
|
191
|
+
if (result.success) {
|
|
192
|
+
return result.data;
|
|
193
|
+
}
|
|
194
|
+
throw new InvalidArgumentError(
|
|
195
|
+
result.error.issues.map((iss) => iss.message).join("\n"),
|
|
196
|
+
);
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function createZipBuffer(mntDataDir: string): Promise<Buffer> {
|
|
201
|
+
return await new Promise((resolve, reject) => {
|
|
202
|
+
const buffers: Buffer[] = [];
|
|
203
|
+
const archive = archiver("zip", {
|
|
204
|
+
zlib: { level: 9 },
|
|
205
|
+
});
|
|
206
|
+
archive.on("data", (chunk: Buffer) => {
|
|
207
|
+
buffers.push(chunk);
|
|
208
|
+
});
|
|
209
|
+
archive.on("end", () => {
|
|
210
|
+
const zipBuffer = Buffer.concat(buffers);
|
|
211
|
+
console.log(`Created zip archive (${zipBuffer.length} total bytes)`);
|
|
212
|
+
resolve(zipBuffer);
|
|
213
|
+
});
|
|
214
|
+
archive.on("error", (err: unknown) => {
|
|
215
|
+
reject(err);
|
|
216
|
+
});
|
|
217
|
+
if (fs.existsSync(mntDataDir) && fs.statSync(mntDataDir).isDirectory()) {
|
|
218
|
+
archive.directory(mntDataDir, false);
|
|
219
|
+
} else {
|
|
220
|
+
reject(
|
|
221
|
+
new Error(
|
|
222
|
+
`Directory ${mntDataDir} does not exist or is not a directory`,
|
|
223
|
+
),
|
|
224
|
+
);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
archive.finalize();
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function uploadData(
|
|
232
|
+
client: Awaited<ReturnType<typeof makeAuthenticatedClient>>,
|
|
233
|
+
mntDataDir: string,
|
|
234
|
+
): Promise<{ secretDataZipGcsPath: string }> {
|
|
235
|
+
console.log("Zipping data...");
|
|
236
|
+
const zipBuffer = await createZipBuffer(mntDataDir);
|
|
237
|
+
console.log("Getting upload URL...");
|
|
238
|
+
const upload = await client.mcpHost.makeSignedUploadUrl.mutate({
|
|
239
|
+
filename: "data.zip",
|
|
240
|
+
});
|
|
241
|
+
console.log("Uploading data...");
|
|
242
|
+
const formData = new FormData();
|
|
243
|
+
for (const [key, value] of Object.entries(upload.fields)) {
|
|
244
|
+
formData.append(key, value);
|
|
245
|
+
}
|
|
246
|
+
const blob = new Blob([new Uint8Array(zipBuffer)], {
|
|
247
|
+
type: "application/zip",
|
|
248
|
+
});
|
|
249
|
+
formData.append("file", blob);
|
|
250
|
+
const uploadResponse = await fetch(upload.url, {
|
|
251
|
+
method: "POST",
|
|
252
|
+
body: formData,
|
|
253
|
+
});
|
|
254
|
+
if (!uploadResponse.ok) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
`Upload failed: ${uploadResponse.status} ${uploadResponse.statusText}`,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
console.log("File uploaded.");
|
|
260
|
+
return { secretDataZipGcsPath: upload.secretDataZipGcsPath };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const program = new Command()
|
|
264
|
+
.name("hosted")
|
|
265
|
+
.description("Control hosted servers on MintMCP")
|
|
266
|
+
.version("0.0.1")
|
|
267
|
+
.option(
|
|
268
|
+
"-e, --env <environment>",
|
|
269
|
+
"target environment",
|
|
270
|
+
argParser(EnvSchema),
|
|
271
|
+
"production",
|
|
272
|
+
)
|
|
273
|
+
.requiredOption("-b, --base <base>", "base directory for config and data");
|
|
274
|
+
|
|
275
|
+
program
|
|
276
|
+
.command("create")
|
|
277
|
+
.description("Create a hosted server.")
|
|
278
|
+
.requiredOption("--name <name>", "Name of the hosted server")
|
|
279
|
+
.option(
|
|
280
|
+
"--startup-command <command>",
|
|
281
|
+
"Command that runs on hosted container to start the server (runs in bash).",
|
|
282
|
+
"cd /mnt && npm run build && npm run start",
|
|
283
|
+
)
|
|
284
|
+
.option(
|
|
285
|
+
"--mnt-data-dir <directory>",
|
|
286
|
+
"Directory to copy to /mnt on the server.",
|
|
287
|
+
".",
|
|
288
|
+
)
|
|
289
|
+
.action(async (options) => {
|
|
290
|
+
const { name, startupCommand, mntDataDir } = options;
|
|
291
|
+
const { env, base } = program.opts();
|
|
292
|
+
|
|
293
|
+
const configPath = path.join(base, HOSTED_CONFIG_PATHS[env]);
|
|
294
|
+
if (fs.existsSync(configPath)) {
|
|
295
|
+
throw new Error("Hosted server config already exists.");
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const client = await makeAuthenticatedClient(env);
|
|
299
|
+
const upload = await uploadData(client, path.join(base, mntDataDir));
|
|
300
|
+
|
|
301
|
+
const ConfigWithoutPathSchema = UserConfigUpdateSchema.omit({secretDataZipGcsPath: true});
|
|
302
|
+
const config = ConfigWithoutPathSchema.parse({
|
|
303
|
+
userGivenName: name,
|
|
304
|
+
command: ["bash", "-c", startupCommand],
|
|
305
|
+
replaceEnv: [],
|
|
306
|
+
} satisfies z.input<typeof ConfigWithoutPathSchema>);
|
|
307
|
+
|
|
308
|
+
console.log("Creating server...");
|
|
309
|
+
const createdServer = await client.mcpHost.createServer.mutate({
|
|
310
|
+
config: { ...config, secretDataZipGcsPath: upload.secretDataZipGcsPath },
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const hostedConfig: HostedConfig = {
|
|
314
|
+
hostedId: createdServer.hostedId,
|
|
315
|
+
config,
|
|
316
|
+
mntDataDir,
|
|
317
|
+
};
|
|
318
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
319
|
+
fs.writeFileSync(configPath, JSON.stringify(hostedConfig, null, 2));
|
|
320
|
+
|
|
321
|
+
console.log(
|
|
322
|
+
`Created ${WEB_CLIENT_URL[env]}/hosted/${createdServer.hostedId}`,
|
|
323
|
+
);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
program
|
|
327
|
+
.command("update")
|
|
328
|
+
.description("Update a hosted server.")
|
|
329
|
+
.action(async () => {
|
|
330
|
+
const { env, base } = program.opts();
|
|
331
|
+
const configPath = path.join(base, HOSTED_CONFIG_PATHS[env]);
|
|
332
|
+
if (!fs.existsSync(configPath)) {
|
|
333
|
+
throw new Error(
|
|
334
|
+
"Hosted server config not found. Run 'create' command first.",
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
const configData = fs.readFileSync(configPath, "utf8");
|
|
338
|
+
const hostedConfig = HostedConfigSchema.parse(JSON.parse(configData));
|
|
339
|
+
|
|
340
|
+
const client = await makeAuthenticatedClient(env);
|
|
341
|
+
const upload = await uploadData(
|
|
342
|
+
client,
|
|
343
|
+
path.join(base, hostedConfig.mntDataDir),
|
|
344
|
+
);
|
|
345
|
+
console.log("Updating server...");
|
|
346
|
+
await client.mcpHost.updateServer.mutate({
|
|
347
|
+
app: hostedConfig.hostedId,
|
|
348
|
+
config: {
|
|
349
|
+
...hostedConfig.config,
|
|
350
|
+
secretDataZipGcsPath: upload.secretDataZipGcsPath,
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
console.log(
|
|
354
|
+
`Updated ${WEB_CLIENT_URL[env]}/hosted/${hostedConfig.hostedId}`,
|
|
355
|
+
);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
program.parse();
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// File Layout
|
|
4
|
+
"rootDir": "./src",
|
|
5
|
+
"outDir": "./dist",
|
|
6
|
+
|
|
7
|
+
// Environment Settings
|
|
8
|
+
"lib": ["esnext"],
|
|
9
|
+
"types": ["node"],
|
|
10
|
+
|
|
11
|
+
// Other Outputs
|
|
12
|
+
"sourceMap": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
|
|
16
|
+
// Stricter Typechecking Options
|
|
17
|
+
"noUncheckedIndexedAccess": true,
|
|
18
|
+
"exactOptionalPropertyTypes": true,
|
|
19
|
+
|
|
20
|
+
// Recommended Options
|
|
21
|
+
"strict": true,
|
|
22
|
+
"jsx": "react-jsx",
|
|
23
|
+
"verbatimModuleSyntax": true,
|
|
24
|
+
"isolatedModules": true,
|
|
25
|
+
"noUncheckedSideEffectImports": true,
|
|
26
|
+
"moduleDetection": "force",
|
|
27
|
+
"skipLibCheck": true,
|
|
28
|
+
|
|
29
|
+
"module": "nodenext"
|
|
30
|
+
}
|
|
31
|
+
}
|