@artifactly/mcp 0.1.11 → 0.1.12
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/README.md
CHANGED
|
@@ -73,6 +73,12 @@ Artifact workflow notes:
|
|
|
73
73
|
- `artifactly_publish_file` infers MIME type from file extension unless `mimeType` is supplied; explicit `mimeType` wins.
|
|
74
74
|
- Image MIME types such as `image/png` publish as `kind: "image"` and can be listed with `kind: "image"`.
|
|
75
75
|
- Files larger than 10 MB are rejected before upload to avoid large in-memory base64 payloads.
|
|
76
|
+
- App-hosting MCP tools wrap the public API-key routes for `web_app` artifacts:
|
|
77
|
+
- `artifactly_list_app_env`, `artifactly_set_app_env`, and `artifactly_delete_app_env` manage redacted per-app env/secrets.
|
|
78
|
+
- `artifactly_publish_app_bundle` accepts source files with inline `content`, `contentBase64`, or local `filePath` values and queues a background build job; Docker work does not run in the MCP call.
|
|
79
|
+
- `artifactly_enqueue_app_build` queues an image build for already-materialized source, and `artifactly_get_app_build_status` polls job status.
|
|
80
|
+
- `artifactly_deploy_app` deploys an already-built image reference.
|
|
81
|
+
- `artifactly_list_custom_domains`, `artifactly_add_custom_domain`, and `artifactly_delete_custom_domain` manage custom-domain status and DNS instructions without exposing provider credentials.
|
|
76
82
|
- Requests time out after 30 seconds.
|
|
77
83
|
- This package currently exposes MCP tools only; resources, resource templates, and prompts are intentionally unsupported.
|
|
78
84
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@artifactly/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"private": false,
|
|
5
5
|
"files": [
|
|
6
6
|
"bin",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"typecheck": "tsc --noEmit",
|
|
22
22
|
"test": "vitest run",
|
|
23
23
|
"artifactly": "tsx src/cli/run-artifactly.ts",
|
|
24
|
+
"app-build-worker": "tsx src/lib/apps/run-build-worker.ts",
|
|
24
25
|
"generate-api-key": "node scripts/generate-api-key.mjs",
|
|
25
26
|
"supabase:start": "supabase start",
|
|
26
27
|
"supabase:reset": "supabase db reset",
|
|
@@ -13,6 +13,9 @@ describe("Artifactly MCP package documentation", () => {
|
|
|
13
13
|
expect(readme).toContain("public`, `unlisted`, `private`, and `gated`");
|
|
14
14
|
expect(readme).toContain("`artifactly_list_artifacts` supports `limit`, `cursor`, `visibility`, and `kind`");
|
|
15
15
|
expect(readme).toContain("Files larger than 10 MB are rejected before upload");
|
|
16
|
+
expect(readme).toContain("artifactly_publish_app_bundle");
|
|
17
|
+
expect(readme).toContain("Docker work does not run in the MCP call");
|
|
18
|
+
expect(readme).toContain("artifactly_add_custom_domain");
|
|
16
19
|
expect(readme).toContain("Requests time out after 30 seconds");
|
|
17
20
|
expect(readme).toContain("`artifactly_get_artifact` returns full content by default");
|
|
18
21
|
expect(readme).toContain("`metadataOnly: true`");
|
|
@@ -85,6 +85,27 @@ describe("Artifactly MCP stdio server helpers", () => {
|
|
|
85
85
|
mimeType: "application/pdf",
|
|
86
86
|
}).success,
|
|
87
87
|
).toBe(true);
|
|
88
|
+
expect(
|
|
89
|
+
server._registeredTools.artifactly_set_app_env.inputSchema.safeParse({
|
|
90
|
+
artifactId: "artifact_1",
|
|
91
|
+
name: "DATABASE_URL",
|
|
92
|
+
value: "postgres://example",
|
|
93
|
+
isSecret: true,
|
|
94
|
+
}).success,
|
|
95
|
+
).toBe(true);
|
|
96
|
+
expect(
|
|
97
|
+
server._registeredTools.artifactly_publish_app_bundle.inputSchema.safeParse({
|
|
98
|
+
artifactId: "artifact_1",
|
|
99
|
+
files: [{ path: "server.js", filePath: "/tmp/server.js" }],
|
|
100
|
+
sourceKind: "nextjs",
|
|
101
|
+
}).success,
|
|
102
|
+
).toBe(true);
|
|
103
|
+
expect(
|
|
104
|
+
server._registeredTools.artifactly_add_custom_domain.inputSchema.safeParse({
|
|
105
|
+
artifactId: "artifact_1",
|
|
106
|
+
hostname: "app.example.com",
|
|
107
|
+
}).success,
|
|
108
|
+
).toBe(true);
|
|
88
109
|
});
|
|
89
110
|
|
|
90
111
|
it("registers MCP tool annotations and accurate content descriptions", () => {
|
|
@@ -101,6 +122,15 @@ describe("Artifactly MCP stdio server helpers", () => {
|
|
|
101
122
|
expect(server._registeredTools.artifactly_delete_artifact.annotations).toMatchObject({
|
|
102
123
|
destructiveHint: true,
|
|
103
124
|
});
|
|
125
|
+
expect(server._registeredTools.artifactly_delete_app_env.annotations).toMatchObject({
|
|
126
|
+
destructiveHint: true,
|
|
127
|
+
});
|
|
128
|
+
expect(server._registeredTools.artifactly_list_app_env.annotations).toMatchObject({
|
|
129
|
+
readOnlyHint: true,
|
|
130
|
+
});
|
|
131
|
+
expect(server._registeredTools.artifactly_list_custom_domains.annotations).toMatchObject({
|
|
132
|
+
readOnlyHint: true,
|
|
133
|
+
});
|
|
104
134
|
expect(server._registeredTools.artifactly_get_artifact.description).toMatch(/full content/i);
|
|
105
135
|
expect(server._registeredTools.artifactly_get_artifact.description).toMatch(/metadataOnly/i);
|
|
106
136
|
});
|
|
@@ -117,6 +147,16 @@ describe("Artifactly MCP stdio server helpers", () => {
|
|
|
117
147
|
"artifactly_update_artifact",
|
|
118
148
|
"artifactly_delete_artifact",
|
|
119
149
|
"artifactly_publish_file",
|
|
150
|
+
"artifactly_list_app_env",
|
|
151
|
+
"artifactly_set_app_env",
|
|
152
|
+
"artifactly_delete_app_env",
|
|
153
|
+
"artifactly_publish_app_bundle",
|
|
154
|
+
"artifactly_enqueue_app_build",
|
|
155
|
+
"artifactly_get_app_build_status",
|
|
156
|
+
"artifactly_deploy_app",
|
|
157
|
+
"artifactly_list_custom_domains",
|
|
158
|
+
"artifactly_add_custom_domain",
|
|
159
|
+
"artifactly_delete_custom_domain",
|
|
120
160
|
]);
|
|
121
161
|
});
|
|
122
162
|
|
|
@@ -145,6 +145,50 @@ const publishFileSchema = z.object({
|
|
|
145
145
|
visibility: z.enum(["public", "unlisted", "private", "gated"]).optional(),
|
|
146
146
|
accessList: z.array(z.string().email()).optional(),
|
|
147
147
|
});
|
|
148
|
+
const appEnvSchema = z.object({
|
|
149
|
+
artifactId: z.string().min(1),
|
|
150
|
+
});
|
|
151
|
+
const setAppEnvSchema = z.object({
|
|
152
|
+
artifactId: z.string().min(1),
|
|
153
|
+
name: z.string().min(1),
|
|
154
|
+
value: z.string(),
|
|
155
|
+
isSecret: z.boolean().optional(),
|
|
156
|
+
});
|
|
157
|
+
const deleteAppEnvSchema = z.object({
|
|
158
|
+
artifactId: z.string().min(1),
|
|
159
|
+
name: z.string().min(1),
|
|
160
|
+
});
|
|
161
|
+
const appBuildSchema = z.object({
|
|
162
|
+
artifactId: z.string().min(1),
|
|
163
|
+
sourceKind: z.enum(["nextjs", "dockerfile"]).optional(),
|
|
164
|
+
tag: z.string().min(1).optional(),
|
|
165
|
+
port: z.number().int().min(1).max(65535).optional(),
|
|
166
|
+
});
|
|
167
|
+
const appBundleFileSchema = z.object({
|
|
168
|
+
path: z.string().min(1),
|
|
169
|
+
content: z.string().optional(),
|
|
170
|
+
contentBase64: z.string().optional(),
|
|
171
|
+
filePath: z.string().min(1).optional(),
|
|
172
|
+
});
|
|
173
|
+
const publishAppBundleSchema = appBuildSchema.extend({
|
|
174
|
+
files: z.array(appBundleFileSchema).min(1),
|
|
175
|
+
});
|
|
176
|
+
const appBuildStatusSchema = z.object({
|
|
177
|
+
artifactId: z.string().min(1),
|
|
178
|
+
jobId: z.string().min(1),
|
|
179
|
+
});
|
|
180
|
+
const deployAppSchema = z.object({
|
|
181
|
+
artifactId: z.string().min(1),
|
|
182
|
+
imageRef: z.string().min(1),
|
|
183
|
+
port: z.number().int().min(1).max(65535).optional(),
|
|
184
|
+
});
|
|
185
|
+
const customDomainSchema = z.object({
|
|
186
|
+
artifactId: z.string().min(1),
|
|
187
|
+
});
|
|
188
|
+
const mutateCustomDomainSchema = z.object({
|
|
189
|
+
artifactId: z.string().min(1),
|
|
190
|
+
hostname: z.string().min(1),
|
|
191
|
+
});
|
|
148
192
|
|
|
149
193
|
const toolSchemas = {
|
|
150
194
|
artifactly_auth_status: authStatusSchema,
|
|
@@ -157,13 +201,28 @@ const toolSchemas = {
|
|
|
157
201
|
artifactly_update_artifact: updateArtifactSchema,
|
|
158
202
|
artifactly_delete_artifact: deleteArtifactSchema,
|
|
159
203
|
artifactly_publish_file: publishFileSchema,
|
|
204
|
+
artifactly_list_app_env: appEnvSchema,
|
|
205
|
+
artifactly_set_app_env: setAppEnvSchema,
|
|
206
|
+
artifactly_delete_app_env: deleteAppEnvSchema,
|
|
207
|
+
artifactly_publish_app_bundle: publishAppBundleSchema,
|
|
208
|
+
artifactly_enqueue_app_build: appBuildSchema,
|
|
209
|
+
artifactly_get_app_build_status: appBuildStatusSchema,
|
|
210
|
+
artifactly_deploy_app: deployAppSchema,
|
|
211
|
+
artifactly_list_custom_domains: customDomainSchema,
|
|
212
|
+
artifactly_add_custom_domain: mutateCustomDomainSchema,
|
|
213
|
+
artifactly_delete_custom_domain: mutateCustomDomainSchema,
|
|
160
214
|
} as const;
|
|
161
215
|
|
|
162
216
|
const toolAnnotations: Record<string, Record<string, boolean>> = {
|
|
163
217
|
artifactly_auth_status: { readOnlyHint: true },
|
|
164
218
|
artifactly_list_artifacts: { readOnlyHint: true },
|
|
165
219
|
artifactly_get_artifact: { readOnlyHint: true },
|
|
220
|
+
artifactly_list_app_env: { readOnlyHint: true },
|
|
221
|
+
artifactly_get_app_build_status: { readOnlyHint: true },
|
|
222
|
+
artifactly_list_custom_domains: { readOnlyHint: true },
|
|
166
223
|
artifactly_delete_artifact: { destructiveHint: true },
|
|
224
|
+
artifactly_delete_app_env: { destructiveHint: true },
|
|
225
|
+
artifactly_delete_custom_domain: { destructiveHint: true },
|
|
167
226
|
artifactly_logout: { destructiveHint: true },
|
|
168
227
|
};
|
|
169
228
|
|
|
@@ -20,6 +20,16 @@ describe("Artifactly MCP tools", () => {
|
|
|
20
20
|
"artifactly_update_artifact",
|
|
21
21
|
"artifactly_delete_artifact",
|
|
22
22
|
"artifactly_publish_file",
|
|
23
|
+
"artifactly_list_app_env",
|
|
24
|
+
"artifactly_set_app_env",
|
|
25
|
+
"artifactly_delete_app_env",
|
|
26
|
+
"artifactly_publish_app_bundle",
|
|
27
|
+
"artifactly_enqueue_app_build",
|
|
28
|
+
"artifactly_get_app_build_status",
|
|
29
|
+
"artifactly_deploy_app",
|
|
30
|
+
"artifactly_list_custom_domains",
|
|
31
|
+
"artifactly_add_custom_domain",
|
|
32
|
+
"artifactly_delete_custom_domain",
|
|
23
33
|
]);
|
|
24
34
|
});
|
|
25
35
|
|
|
@@ -639,4 +649,129 @@ describe("Artifactly MCP tools", () => {
|
|
|
639
649
|
});
|
|
640
650
|
rmSync(dir, { recursive: true, force: true });
|
|
641
651
|
});
|
|
652
|
+
|
|
653
|
+
it("wraps API-key app-hosting endpoints without returning secrets", async () => {
|
|
654
|
+
const dir = mkdtempSync(join(tmpdir(), "artifactly-mcp-"));
|
|
655
|
+
const credentialPath = join(dir, "credentials.json");
|
|
656
|
+
const requests: Array<{ method: string; path: string; body?: unknown; apiKey?: string }> = [];
|
|
657
|
+
writeCredentials(
|
|
658
|
+
{
|
|
659
|
+
apiBaseUrl: "https://artifactly.example",
|
|
660
|
+
apiKey: "art_test_secret",
|
|
661
|
+
email: "agent@example.com",
|
|
662
|
+
workspace: "agent",
|
|
663
|
+
},
|
|
664
|
+
credentialPath,
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
const requestJson = async (method: string, path: string, body?: unknown, options?: { apiKey?: string }) => {
|
|
668
|
+
requests.push({ method, path, body, apiKey: options?.apiKey });
|
|
669
|
+
if (path.endsWith("/app/env") && method === "POST") {
|
|
670
|
+
return { env: { name: "DATABASE_URL", value: "[REDACTED]", isSecret: true } };
|
|
671
|
+
}
|
|
672
|
+
if (path.endsWith("/app/build-jobs") && method === "POST") {
|
|
673
|
+
return { job: { id: "job_1", status: "queued", imageRef: "registry.fly.io/app:test" } };
|
|
674
|
+
}
|
|
675
|
+
return { ok: true };
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
await expect(callArtifactlyMcpTool({
|
|
679
|
+
name: "artifactly_list_app_env",
|
|
680
|
+
credentialPath,
|
|
681
|
+
arguments: { artifactId: "artifact_1" },
|
|
682
|
+
requestJson,
|
|
683
|
+
})).resolves.toEqual({ ok: true });
|
|
684
|
+
await expect(callArtifactlyMcpTool({
|
|
685
|
+
name: "artifactly_set_app_env",
|
|
686
|
+
credentialPath,
|
|
687
|
+
arguments: { artifactId: "artifact_1", name: "DATABASE_URL", value: "postgres://secret", isSecret: true },
|
|
688
|
+
requestJson,
|
|
689
|
+
})).resolves.toEqual({ env: { name: "DATABASE_URL", value: "[REDACTED]", isSecret: true } });
|
|
690
|
+
await callArtifactlyMcpTool({
|
|
691
|
+
name: "artifactly_delete_app_env",
|
|
692
|
+
credentialPath,
|
|
693
|
+
arguments: { artifactId: "artifact_1", name: "DATABASE_URL" },
|
|
694
|
+
requestJson,
|
|
695
|
+
});
|
|
696
|
+
await expect(callArtifactlyMcpTool({
|
|
697
|
+
name: "artifactly_enqueue_app_build",
|
|
698
|
+
credentialPath,
|
|
699
|
+
arguments: { artifactId: "artifact_1", sourceKind: "nextjs", tag: "test", port: 3000 },
|
|
700
|
+
requestJson,
|
|
701
|
+
})).resolves.toMatchObject({ job: { id: "job_1", status: "queued" } });
|
|
702
|
+
await callArtifactlyMcpTool({
|
|
703
|
+
name: "artifactly_get_app_build_status",
|
|
704
|
+
credentialPath,
|
|
705
|
+
arguments: { artifactId: "artifact_1", jobId: "job_1" },
|
|
706
|
+
requestJson,
|
|
707
|
+
});
|
|
708
|
+
await callArtifactlyMcpTool({
|
|
709
|
+
name: "artifactly_deploy_app",
|
|
710
|
+
credentialPath,
|
|
711
|
+
arguments: { artifactId: "artifact_1", imageRef: "registry.fly.io/app:test", port: 3000 },
|
|
712
|
+
requestJson,
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
expect(requests).toEqual([
|
|
716
|
+
{ method: "GET", path: "/api/artifacts/artifact_1/app/env", apiKey: "art_test_secret" },
|
|
717
|
+
{ method: "POST", path: "/api/artifacts/artifact_1/app/env", apiKey: "art_test_secret", body: { name: "DATABASE_URL", value: "postgres://secret", isSecret: true } },
|
|
718
|
+
{ method: "DELETE", path: "/api/artifacts/artifact_1/app/env?name=DATABASE_URL", apiKey: "art_test_secret" },
|
|
719
|
+
{ method: "POST", path: "/api/artifacts/artifact_1/app/build-jobs", apiKey: "art_test_secret", body: { sourceKind: "nextjs", tag: "test", port: 3000 } },
|
|
720
|
+
{ method: "GET", path: "/api/artifacts/artifact_1/app/build-jobs/job_1", apiKey: "art_test_secret" },
|
|
721
|
+
{ method: "POST", path: "/api/artifacts/artifact_1/app/deploy", apiKey: "art_test_secret", body: { imageRef: "registry.fly.io/app:test", port: 3000 } },
|
|
722
|
+
]);
|
|
723
|
+
rmSync(dir, { recursive: true, force: true });
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
it("uploads app bundle files and manages custom domains through public API routes", async () => {
|
|
727
|
+
const dir = mkdtempSync(join(tmpdir(), "artifactly-mcp-"));
|
|
728
|
+
const credentialPath = join(dir, "credentials.json");
|
|
729
|
+
const filePath = join(dir, "server.js");
|
|
730
|
+
const requests: Array<{ method: string; path: string; body?: unknown; apiKey?: string }> = [];
|
|
731
|
+
writeFileSync(filePath, "console.log('hi')\n");
|
|
732
|
+
writeCredentials(
|
|
733
|
+
{
|
|
734
|
+
apiBaseUrl: "https://artifactly.example",
|
|
735
|
+
apiKey: "art_test_secret",
|
|
736
|
+
email: "agent@example.com",
|
|
737
|
+
workspace: "agent",
|
|
738
|
+
},
|
|
739
|
+
credentialPath,
|
|
740
|
+
);
|
|
741
|
+
|
|
742
|
+
const requestJson = async (method: string, path: string, body?: unknown, options?: { apiKey?: string }) => {
|
|
743
|
+
requests.push({ method, path, body, apiKey: options?.apiKey });
|
|
744
|
+
return path.includes("custom-domains") ? { domains: [], dns: { target: "app.fly.dev" } } : { job: { id: "job_1" } };
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
await callArtifactlyMcpTool({
|
|
748
|
+
name: "artifactly_publish_app_bundle",
|
|
749
|
+
credentialPath,
|
|
750
|
+
arguments: {
|
|
751
|
+
artifactId: "artifact_1",
|
|
752
|
+
sourceKind: "nextjs",
|
|
753
|
+
files: [{ path: "server.js", filePath }],
|
|
754
|
+
},
|
|
755
|
+
requestJson,
|
|
756
|
+
});
|
|
757
|
+
await callArtifactlyMcpTool({ name: "artifactly_list_custom_domains", credentialPath, arguments: { artifactId: "artifact_1" }, requestJson });
|
|
758
|
+
await callArtifactlyMcpTool({ name: "artifactly_add_custom_domain", credentialPath, arguments: { artifactId: "artifact_1", hostname: "app.example.com" }, requestJson });
|
|
759
|
+
await callArtifactlyMcpTool({ name: "artifactly_delete_custom_domain", credentialPath, arguments: { artifactId: "artifact_1", hostname: "app.example.com" }, requestJson });
|
|
760
|
+
|
|
761
|
+
expect(requests).toEqual([
|
|
762
|
+
{
|
|
763
|
+
method: "POST",
|
|
764
|
+
path: "/api/artifacts/artifact_1/app/publish",
|
|
765
|
+
apiKey: "art_test_secret",
|
|
766
|
+
body: {
|
|
767
|
+
sourceKind: "nextjs",
|
|
768
|
+
files: [{ path: "server.js", contentBase64: "Y29uc29sZS5sb2coJ2hpJykK" }],
|
|
769
|
+
},
|
|
770
|
+
},
|
|
771
|
+
{ method: "GET", path: "/api/artifacts/artifact_1/custom-domains", apiKey: "art_test_secret" },
|
|
772
|
+
{ method: "POST", path: "/api/artifacts/artifact_1/custom-domains", apiKey: "art_test_secret", body: { hostname: "app.example.com" } },
|
|
773
|
+
{ method: "DELETE", path: "/api/artifacts/artifact_1/custom-domains?hostname=app.example.com", apiKey: "art_test_secret" },
|
|
774
|
+
]);
|
|
775
|
+
rmSync(dir, { recursive: true, force: true });
|
|
776
|
+
});
|
|
642
777
|
});
|
|
@@ -204,6 +204,47 @@ function numberArg(args: Record<string, unknown> | undefined, key: string): numb
|
|
|
204
204
|
return undefined;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
function appBuildPayload(args: Record<string, unknown> | undefined) {
|
|
208
|
+
const sourceKind = stringArg(args, "sourceKind");
|
|
209
|
+
const tag = stringArg(args, "tag");
|
|
210
|
+
const port = numberArg(args, "port");
|
|
211
|
+
return {
|
|
212
|
+
...(sourceKind ? { sourceKind } : {}),
|
|
213
|
+
...(tag ? { tag } : {}),
|
|
214
|
+
...(port !== undefined ? { port } : {}),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function appBundleFilesArg(args: Record<string, unknown> | undefined) {
|
|
219
|
+
const value = args?.files;
|
|
220
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
221
|
+
throw new Error("Missing required argument: files");
|
|
222
|
+
}
|
|
223
|
+
return value.map((item) => {
|
|
224
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
225
|
+
throw new Error("Each bundle file must be an object.");
|
|
226
|
+
}
|
|
227
|
+
const file = item as Record<string, unknown>;
|
|
228
|
+
const path = typeof file.path === "string" && file.path.trim() ? file.path.trim() : "";
|
|
229
|
+
if (!path) {
|
|
230
|
+
throw new Error("Each bundle file requires a path.");
|
|
231
|
+
}
|
|
232
|
+
const content = typeof file.content === "string" ? file.content : undefined;
|
|
233
|
+
const contentBase64 = typeof file.contentBase64 === "string" ? file.contentBase64 : undefined;
|
|
234
|
+
const filePath = typeof file.filePath === "string" && file.filePath.trim() ? file.filePath.trim() : undefined;
|
|
235
|
+
if (filePath) {
|
|
236
|
+
return { path, contentBase64: readFileForPublish(filePath).toString("base64") };
|
|
237
|
+
}
|
|
238
|
+
if (content !== undefined) {
|
|
239
|
+
return { path, content };
|
|
240
|
+
}
|
|
241
|
+
if (contentBase64 !== undefined) {
|
|
242
|
+
return { path, contentBase64 };
|
|
243
|
+
}
|
|
244
|
+
throw new Error(`Bundle file ${path} must include content, contentBase64, or filePath.`);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
207
248
|
export function listArtifactlyMcpTools(): ArtifactlyMcpTool[] {
|
|
208
249
|
return [
|
|
209
250
|
{
|
|
@@ -319,6 +360,79 @@ export function listArtifactlyMcpTools(): ArtifactlyMcpTool[] {
|
|
|
319
360
|
required: ["path"],
|
|
320
361
|
},
|
|
321
362
|
},
|
|
363
|
+
{
|
|
364
|
+
name: "artifactly_list_app_env",
|
|
365
|
+
description: "List redacted environment variables for an Artifactly web_app artifact.",
|
|
366
|
+
inputSchema: { type: "object", properties: { artifactId: { type: "string" } }, required: ["artifactId"] },
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
name: "artifactly_set_app_env",
|
|
370
|
+
description: "Create or update a web_app environment variable. Secret values are never returned by Artifactly.",
|
|
371
|
+
inputSchema: {
|
|
372
|
+
type: "object",
|
|
373
|
+
properties: {
|
|
374
|
+
artifactId: { type: "string" },
|
|
375
|
+
name: { type: "string" },
|
|
376
|
+
value: { type: "string" },
|
|
377
|
+
isSecret: { type: "boolean" },
|
|
378
|
+
},
|
|
379
|
+
required: ["artifactId", "name", "value"],
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
name: "artifactly_delete_app_env",
|
|
384
|
+
description: "Delete a web_app environment variable.",
|
|
385
|
+
inputSchema: { type: "object", properties: { artifactId: { type: "string" }, name: { type: "string" } }, required: ["artifactId", "name"] },
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: "artifactly_publish_app_bundle",
|
|
389
|
+
description: "Materialize source files for a web_app artifact and enqueue a background image build job. Files can include inline content, contentBase64, or local filePath values; local files larger than 10 MB are rejected before upload.",
|
|
390
|
+
inputSchema: {
|
|
391
|
+
type: "object",
|
|
392
|
+
properties: {
|
|
393
|
+
artifactId: { type: "string" },
|
|
394
|
+
sourceKind: { type: "string", enum: ["nextjs", "dockerfile"] },
|
|
395
|
+
tag: { type: "string" },
|
|
396
|
+
port: { type: "number" },
|
|
397
|
+
files: { type: "array", items: { type: "object" } },
|
|
398
|
+
},
|
|
399
|
+
required: ["artifactId", "files"],
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
name: "artifactly_enqueue_app_build",
|
|
404
|
+
description: "Queue a persistent background image build job for an already-materialized web_app source directory.",
|
|
405
|
+
inputSchema: {
|
|
406
|
+
type: "object",
|
|
407
|
+
properties: { artifactId: { type: "string" }, sourceKind: { type: "string", enum: ["nextjs", "dockerfile"] }, tag: { type: "string" }, port: { type: "number" } },
|
|
408
|
+
required: ["artifactId"],
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: "artifactly_get_app_build_status",
|
|
413
|
+
description: "Get a background app image build job status.",
|
|
414
|
+
inputSchema: { type: "object", properties: { artifactId: { type: "string" }, jobId: { type: "string" } }, required: ["artifactId", "jobId"] },
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
name: "artifactly_deploy_app",
|
|
418
|
+
description: "Deploy an already-built app image reference for a web_app artifact.",
|
|
419
|
+
inputSchema: { type: "object", properties: { artifactId: { type: "string" }, imageRef: { type: "string" }, port: { type: "number" } }, required: ["artifactId", "imageRef"] },
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
name: "artifactly_list_custom_domains",
|
|
423
|
+
description: "List custom domains and statuses for a web_app artifact.",
|
|
424
|
+
inputSchema: { type: "object", properties: { artifactId: { type: "string" } }, required: ["artifactId"] },
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
name: "artifactly_add_custom_domain",
|
|
428
|
+
description: "Add or refresh a custom domain for a web_app artifact and return DNS instructions/status.",
|
|
429
|
+
inputSchema: { type: "object", properties: { artifactId: { type: "string" }, hostname: { type: "string" } }, required: ["artifactId", "hostname"] },
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
name: "artifactly_delete_custom_domain",
|
|
433
|
+
description: "Delete a custom domain from a web_app artifact.",
|
|
434
|
+
inputSchema: { type: "object", properties: { artifactId: { type: "string" }, hostname: { type: "string" } }, required: ["artifactId", "hostname"] },
|
|
435
|
+
},
|
|
322
436
|
];
|
|
323
437
|
}
|
|
324
438
|
|
|
@@ -539,5 +653,75 @@ export async function callArtifactlyMcpTool(input: CallToolInput) {
|
|
|
539
653
|
);
|
|
540
654
|
}
|
|
541
655
|
|
|
656
|
+
if (input.name === "artifactly_list_app_env") {
|
|
657
|
+
if (!credentials) throw new Error(LOGIN_GUIDANCE);
|
|
658
|
+
const artifactId = requireStringArg(input.arguments, "artifactId");
|
|
659
|
+
return objectResult(await requestJson("GET", `/api/artifacts/${artifactId}/app/env`, undefined, { apiKey: credentials.apiKey }));
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (input.name === "artifactly_set_app_env") {
|
|
663
|
+
if (!credentials) throw new Error(LOGIN_GUIDANCE);
|
|
664
|
+
const artifactId = requireStringArg(input.arguments, "artifactId");
|
|
665
|
+
const name = requireStringArg(input.arguments, "name");
|
|
666
|
+
const value = requireRawStringArg(input.arguments, "value");
|
|
667
|
+
const isSecret = booleanArg(input.arguments, "isSecret");
|
|
668
|
+
return objectResult(await requestJson("POST", `/api/artifacts/${artifactId}/app/env`, { name, value, isSecret }, { apiKey: credentials.apiKey }));
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (input.name === "artifactly_delete_app_env") {
|
|
672
|
+
if (!credentials) throw new Error(LOGIN_GUIDANCE);
|
|
673
|
+
const artifactId = requireStringArg(input.arguments, "artifactId");
|
|
674
|
+
const name = requireStringArg(input.arguments, "name");
|
|
675
|
+
return objectResult(await requestJson("DELETE", `/api/artifacts/${artifactId}/app/env${queryString({ name })}`, undefined, { apiKey: credentials.apiKey }));
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (input.name === "artifactly_publish_app_bundle") {
|
|
679
|
+
if (!credentials) throw new Error(LOGIN_GUIDANCE);
|
|
680
|
+
const artifactId = requireStringArg(input.arguments, "artifactId");
|
|
681
|
+
const files = appBundleFilesArg(input.arguments);
|
|
682
|
+
return objectResult(await requestJson("POST", `/api/artifacts/${artifactId}/app/publish`, { ...appBuildPayload(input.arguments), files }, { apiKey: credentials.apiKey }));
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (input.name === "artifactly_enqueue_app_build") {
|
|
686
|
+
if (!credentials) throw new Error(LOGIN_GUIDANCE);
|
|
687
|
+
const artifactId = requireStringArg(input.arguments, "artifactId");
|
|
688
|
+
return objectResult(await requestJson("POST", `/api/artifacts/${artifactId}/app/build-jobs`, appBuildPayload(input.arguments), { apiKey: credentials.apiKey }));
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (input.name === "artifactly_get_app_build_status") {
|
|
692
|
+
if (!credentials) throw new Error(LOGIN_GUIDANCE);
|
|
693
|
+
const artifactId = requireStringArg(input.arguments, "artifactId");
|
|
694
|
+
const jobId = requireStringArg(input.arguments, "jobId");
|
|
695
|
+
return objectResult(await requestJson("GET", `/api/artifacts/${artifactId}/app/build-jobs/${jobId}`, undefined, { apiKey: credentials.apiKey }));
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (input.name === "artifactly_deploy_app") {
|
|
699
|
+
if (!credentials) throw new Error(LOGIN_GUIDANCE);
|
|
700
|
+
const artifactId = requireStringArg(input.arguments, "artifactId");
|
|
701
|
+
const imageRef = requireStringArg(input.arguments, "imageRef");
|
|
702
|
+
const port = numberArg(input.arguments, "port");
|
|
703
|
+
return objectResult(await requestJson("POST", `/api/artifacts/${artifactId}/app/deploy`, { imageRef, ...(port !== undefined ? { port } : {}) }, { apiKey: credentials.apiKey }));
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (input.name === "artifactly_list_custom_domains") {
|
|
707
|
+
if (!credentials) throw new Error(LOGIN_GUIDANCE);
|
|
708
|
+
const artifactId = requireStringArg(input.arguments, "artifactId");
|
|
709
|
+
return objectResult(await requestJson("GET", `/api/artifacts/${artifactId}/custom-domains`, undefined, { apiKey: credentials.apiKey }));
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (input.name === "artifactly_add_custom_domain") {
|
|
713
|
+
if (!credentials) throw new Error(LOGIN_GUIDANCE);
|
|
714
|
+
const artifactId = requireStringArg(input.arguments, "artifactId");
|
|
715
|
+
const hostname = requireStringArg(input.arguments, "hostname");
|
|
716
|
+
return objectResult(await requestJson("POST", `/api/artifacts/${artifactId}/custom-domains`, { hostname }, { apiKey: credentials.apiKey }));
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (input.name === "artifactly_delete_custom_domain") {
|
|
720
|
+
if (!credentials) throw new Error(LOGIN_GUIDANCE);
|
|
721
|
+
const artifactId = requireStringArg(input.arguments, "artifactId");
|
|
722
|
+
const hostname = requireStringArg(input.arguments, "hostname");
|
|
723
|
+
return objectResult(await requestJson("DELETE", `/api/artifacts/${artifactId}/custom-domains${queryString({ hostname })}`, undefined, { apiKey: credentials.apiKey }));
|
|
724
|
+
}
|
|
725
|
+
|
|
542
726
|
throw new Error(`Unknown Artifactly MCP tool: ${input.name}`);
|
|
543
727
|
}
|