@artifactly/mcp 0.1.8 → 0.1.9
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 +37 -0
- package/package.json +1 -1
- package/src/cli/artifactly-cli.test.ts +20 -0
- package/src/cli/artifactly-cli.ts +15 -2
- package/src/mcp/artifactly-mcp-docs.test.ts +18 -0
- package/src/mcp/artifactly-mcp-server.test.ts +16 -0
- package/src/mcp/artifactly-mcp-server.ts +7 -1
- package/src/mcp/artifactly-mcp.test.ts +91 -3
- package/src/mcp/artifactly-mcp.ts +57 -6
package/README.md
CHANGED
|
@@ -35,6 +35,43 @@ npx -y --package=@artifactly/mcp artifactly-mcp
|
|
|
35
35
|
|
|
36
36
|
The package also exposes a human CLI as `artifactly`.
|
|
37
37
|
|
|
38
|
+
## Agent MCP usage
|
|
39
|
+
|
|
40
|
+
Typical MCP client config:
|
|
41
|
+
|
|
42
|
+
```toml
|
|
43
|
+
[mcp_servers.artifactly]
|
|
44
|
+
command = "npx"
|
|
45
|
+
args = ["-y", "@artifactly/mcp"]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Robust explicit-bin form:
|
|
49
|
+
|
|
50
|
+
```toml
|
|
51
|
+
[mcp_servers.artifactly]
|
|
52
|
+
command = "npx"
|
|
53
|
+
args = ["-y", "--package=@artifactly/mcp", "artifactly-mcp"]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Authentication flow:
|
|
57
|
+
|
|
58
|
+
1. Run `artifactly_request_login_code` with an email address.
|
|
59
|
+
2. Run `artifactly_verify_login_code` with the emailed code.
|
|
60
|
+
3. Use `artifactly_auth_status` to inspect local credentials, or pass `validate: true` to confirm the saved API key with the Artifactly server.
|
|
61
|
+
4. Run `artifactly_logout` to clear local credentials.
|
|
62
|
+
|
|
63
|
+
Artifact workflow notes:
|
|
64
|
+
|
|
65
|
+
- Default visibility is `unlisted` for agent-created artifacts.
|
|
66
|
+
- Supported visibility values are `public`, `unlisted`, `private`, and `gated`.
|
|
67
|
+
- `gated` artifacts can be published with an `accessList` of allowed email addresses or updated later with `artifactly_update_artifact`.
|
|
68
|
+
- `artifactly_list_artifacts` supports `limit`, `cursor`, `visibility`, and `kind` filters and omits large artifact content by default.
|
|
69
|
+
- `artifactly_get_artifact` returns full owner-visible artifact details.
|
|
70
|
+
- `artifactly_update_artifact` accepts either `markdown` or `content`, not both; pass `description: ""` to clear a description.
|
|
71
|
+
- `artifactly_publish_file` infers MIME type from file extension unless `mimeType` is supplied; explicit `mimeType` wins.
|
|
72
|
+
- Files larger than 10 MB are rejected before upload to avoid large in-memory base64 payloads.
|
|
73
|
+
- Requests time out after 30 seconds.
|
|
74
|
+
|
|
38
75
|
Manual publish from a logged-in maintainer account:
|
|
39
76
|
|
|
40
77
|
```bash
|
package/package.json
CHANGED
|
@@ -6,6 +6,7 @@ import { describe, expect, it } from "vitest";
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
createArtifactlyCli,
|
|
9
|
+
createDefaultPostJson,
|
|
9
10
|
getDefaultCredentialPath,
|
|
10
11
|
readCredentials,
|
|
11
12
|
} from "./artifactly-cli";
|
|
@@ -107,4 +108,23 @@ describe("artifactly CLI", () => {
|
|
|
107
108
|
"/Users/alice/.artifactly/credentials.json",
|
|
108
109
|
);
|
|
109
110
|
});
|
|
111
|
+
|
|
112
|
+
it("uses an explicit timeout signal for default POST requests", async () => {
|
|
113
|
+
const calls: Array<{ url: string; signal?: AbortSignal }> = [];
|
|
114
|
+
const postJson = createDefaultPostJson("https://artifactly.example", {
|
|
115
|
+
timeoutMs: 1234,
|
|
116
|
+
fetcher: async (url, init) => {
|
|
117
|
+
calls.push({ url: String(url), signal: init?.signal ?? undefined });
|
|
118
|
+
return new Response(JSON.stringify({ ok: true }), {
|
|
119
|
+
status: 200,
|
|
120
|
+
headers: { "content-type": "application/json" },
|
|
121
|
+
});
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
await expect(postJson("/api/test", { ok: true })).resolves.toEqual({ ok: true });
|
|
126
|
+
expect(calls).toEqual([
|
|
127
|
+
{ url: "https://artifactly.example/api/test", signal: expect.any(AbortSignal) },
|
|
128
|
+
]);
|
|
129
|
+
});
|
|
110
130
|
});
|
|
@@ -17,6 +17,13 @@ export type PostJson = (
|
|
|
17
17
|
options?: { apiKey?: string },
|
|
18
18
|
) => Promise<unknown>;
|
|
19
19
|
|
|
20
|
+
type Fetcher = typeof fetch;
|
|
21
|
+
|
|
22
|
+
type DefaultPostJsonOptions = {
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
fetcher?: Fetcher;
|
|
25
|
+
};
|
|
26
|
+
|
|
20
27
|
type ArtifactlyCliOptions = {
|
|
21
28
|
apiBaseUrl?: string;
|
|
22
29
|
credentialPath?: string;
|
|
@@ -109,15 +116,21 @@ async function defaultPrompt(question: string) {
|
|
|
109
116
|
}
|
|
110
117
|
}
|
|
111
118
|
|
|
112
|
-
export function createDefaultPostJson(
|
|
119
|
+
export function createDefaultPostJson(
|
|
120
|
+
apiBaseUrl: string,
|
|
121
|
+
options: DefaultPostJsonOptions = {},
|
|
122
|
+
): PostJson {
|
|
123
|
+
const fetcher = options.fetcher ?? fetch;
|
|
124
|
+
const timeoutMs = options.timeoutMs ?? 30_000;
|
|
113
125
|
return async (path, body, options) => {
|
|
114
|
-
const response = await
|
|
126
|
+
const response = await fetcher(`${apiBaseUrl}${path}`, {
|
|
115
127
|
method: "POST",
|
|
116
128
|
headers: {
|
|
117
129
|
"content-type": "application/json",
|
|
118
130
|
...(options?.apiKey ? { authorization: `Bearer ${options.apiKey}` } : {}),
|
|
119
131
|
},
|
|
120
132
|
body: JSON.stringify(body),
|
|
133
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
121
134
|
});
|
|
122
135
|
const payload = (await response.json().catch(() => ({}))) as unknown;
|
|
123
136
|
if (!response.ok) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
describe("Artifactly MCP package documentation", () => {
|
|
6
|
+
it("documents installed-package behavior for agents", () => {
|
|
7
|
+
const readme = readFileSync("README.md", "utf8");
|
|
8
|
+
|
|
9
|
+
expect(readme).toContain("## Agent MCP usage");
|
|
10
|
+
expect(readme).toContain("artifactly_request_login_code");
|
|
11
|
+
expect(readme).toContain("artifactly_verify_login_code");
|
|
12
|
+
expect(readme).toContain("Default visibility is `unlisted`");
|
|
13
|
+
expect(readme).toContain("public`, `unlisted`, `private`, and `gated`");
|
|
14
|
+
expect(readme).toContain("`artifactly_list_artifacts` supports `limit`, `cursor`, `visibility`, and `kind`");
|
|
15
|
+
expect(readme).toContain("Files larger than 10 MB are rejected before upload");
|
|
16
|
+
expect(readme).toContain("Requests time out after 30 seconds");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -55,6 +55,22 @@ describe("Artifactly MCP stdio server helpers", () => {
|
|
|
55
55
|
description: "",
|
|
56
56
|
}).success,
|
|
57
57
|
).toBe(true);
|
|
58
|
+
expect(
|
|
59
|
+
server._registeredTools.artifactly_publish_markdown.inputSchema.safeParse({
|
|
60
|
+
title: "Gated",
|
|
61
|
+
markdown: "# Secret",
|
|
62
|
+
visibility: "gated",
|
|
63
|
+
accessList: ["agent@example.com"],
|
|
64
|
+
}).success,
|
|
65
|
+
).toBe(true);
|
|
66
|
+
expect(
|
|
67
|
+
server._registeredTools.artifactly_list_artifacts.inputSchema.safeParse({
|
|
68
|
+
limit: 10,
|
|
69
|
+
cursor: "2026-02-01T00:00:00.000Z",
|
|
70
|
+
visibility: "gated",
|
|
71
|
+
kind: "html",
|
|
72
|
+
}).success,
|
|
73
|
+
).toBe(true);
|
|
58
74
|
});
|
|
59
75
|
|
|
60
76
|
it("maps Artifactly tools to MCP-compatible tool definitions", () => {
|
|
@@ -100,8 +100,14 @@ const publishMarkdownSchema = z.object({
|
|
|
100
100
|
title: z.string().min(1),
|
|
101
101
|
markdown: z.string().min(1),
|
|
102
102
|
visibility: z.enum(["public", "unlisted", "private", "gated"]).optional(),
|
|
103
|
+
accessList: z.array(z.string().email()).optional(),
|
|
104
|
+
});
|
|
105
|
+
const listArtifactsSchema = z.object({
|
|
106
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
107
|
+
cursor: z.string().optional(),
|
|
108
|
+
visibility: z.enum(["public", "unlisted", "private", "gated"]).optional(),
|
|
109
|
+
kind: z.enum(["markdown", "html", "image", "file", "json", "web_app"]).optional(),
|
|
103
110
|
});
|
|
104
|
-
const listArtifactsSchema = z.object({});
|
|
105
111
|
const getArtifactSchema = z.object({
|
|
106
112
|
artifactId: z.string().min(1),
|
|
107
113
|
});
|
|
@@ -174,6 +174,58 @@ describe("Artifactly MCP tools", () => {
|
|
|
174
174
|
rmSync(dir, { recursive: true, force: true });
|
|
175
175
|
});
|
|
176
176
|
|
|
177
|
+
it("publishes gated markdown with access emails in one call", async () => {
|
|
178
|
+
const dir = mkdtempSync(join(tmpdir(), "artifactly-mcp-"));
|
|
179
|
+
const credentialPath = join(dir, "credentials.json");
|
|
180
|
+
const requests: Array<{ path: string; body: unknown; apiKey?: string }> = [];
|
|
181
|
+
writeCredentials(
|
|
182
|
+
{
|
|
183
|
+
apiBaseUrl: "https://artifactly.example",
|
|
184
|
+
apiKey: "art_t...et",
|
|
185
|
+
email: "agent@example.com",
|
|
186
|
+
workspace: "agent",
|
|
187
|
+
},
|
|
188
|
+
credentialPath,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
await expect(
|
|
192
|
+
callArtifactlyMcpTool({
|
|
193
|
+
name: "artifactly_publish_markdown",
|
|
194
|
+
credentialPath,
|
|
195
|
+
arguments: {
|
|
196
|
+
title: "Gated",
|
|
197
|
+
markdown: "# Secret",
|
|
198
|
+
visibility: "gated",
|
|
199
|
+
accessList: ["One@Example.com", "two@example.com"],
|
|
200
|
+
},
|
|
201
|
+
postJson: async (path, body, options) => {
|
|
202
|
+
requests.push({ path, body, apiKey: options?.apiKey });
|
|
203
|
+
return {
|
|
204
|
+
id: "artifact_1",
|
|
205
|
+
accessList: ["one@example.com", "two@example.com"],
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
}),
|
|
209
|
+
).resolves.toMatchObject({
|
|
210
|
+
id: "artifact_1",
|
|
211
|
+
accessList: ["one@example.com", "two@example.com"],
|
|
212
|
+
});
|
|
213
|
+
expect(requests).toEqual([
|
|
214
|
+
{
|
|
215
|
+
path: "/api/artifacts",
|
|
216
|
+
apiKey: "art_t...et",
|
|
217
|
+
body: {
|
|
218
|
+
title: "Gated",
|
|
219
|
+
content: "# Secret",
|
|
220
|
+
kind: "markdown",
|
|
221
|
+
visibility: "gated",
|
|
222
|
+
accessList: ["One@Example.com", "two@example.com"],
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
]);
|
|
226
|
+
rmSync(dir, { recursive: true, force: true });
|
|
227
|
+
});
|
|
228
|
+
|
|
177
229
|
it("preserves markdown body whitespace when publishing inline markdown", async () => {
|
|
178
230
|
const dir = mkdtempSync(join(tmpdir(), "artifactly-mcp-"));
|
|
179
231
|
const credentialPath = join(dir, "credentials.json");
|
|
@@ -218,7 +270,7 @@ describe("Artifactly MCP tools", () => {
|
|
|
218
270
|
|
|
219
271
|
const requestJson = async (method: string, path: string, body?: unknown, options?: { apiKey?: string }) => {
|
|
220
272
|
requests.push({ method, path, body, apiKey: options?.apiKey });
|
|
221
|
-
if (method === "GET" && path
|
|
273
|
+
if (method === "GET" && path.startsWith("/api/artifacts?")) {
|
|
222
274
|
return { artifacts: [{ id: "artifact_1", title: "Hello" }] };
|
|
223
275
|
}
|
|
224
276
|
if (method === "GET" && path === "/api/artifacts/artifact_1") {
|
|
@@ -231,7 +283,12 @@ describe("Artifactly MCP tools", () => {
|
|
|
231
283
|
};
|
|
232
284
|
|
|
233
285
|
await expect(
|
|
234
|
-
callArtifactlyMcpTool({
|
|
286
|
+
callArtifactlyMcpTool({
|
|
287
|
+
name: "artifactly_list_artifacts",
|
|
288
|
+
credentialPath,
|
|
289
|
+
arguments: { limit: 10, visibility: "gated", kind: "html", cursor: "2026-02-01T00:00:00.000Z" },
|
|
290
|
+
requestJson,
|
|
291
|
+
}),
|
|
235
292
|
).resolves.toEqual({ artifacts: [{ id: "artifact_1", title: "Hello" }] });
|
|
236
293
|
await expect(
|
|
237
294
|
callArtifactlyMcpTool({
|
|
@@ -264,7 +321,12 @@ describe("Artifactly MCP tools", () => {
|
|
|
264
321
|
).resolves.toEqual({ ok: true, artifactId: "artifact_1" });
|
|
265
322
|
|
|
266
323
|
expect(requests).toEqual([
|
|
267
|
-
{
|
|
324
|
+
{
|
|
325
|
+
method: "GET",
|
|
326
|
+
path: "/api/artifacts?limit=10&cursor=2026-02-01T00%3A00%3A00.000Z&visibility=gated&kind=html",
|
|
327
|
+
apiKey: "art_t...et",
|
|
328
|
+
body: undefined,
|
|
329
|
+
},
|
|
268
330
|
{ method: "GET", path: "/api/artifacts/artifact_1", apiKey: "art_t...et", body: undefined },
|
|
269
331
|
{
|
|
270
332
|
method: "PATCH",
|
|
@@ -417,4 +479,30 @@ describe("Artifactly MCP tools", () => {
|
|
|
417
479
|
|
|
418
480
|
rmSync(dir, { recursive: true, force: true });
|
|
419
481
|
});
|
|
482
|
+
|
|
483
|
+
it("rejects oversized files before reading them into base64 payloads", async () => {
|
|
484
|
+
const dir = mkdtempSync(join(tmpdir(), "artifactly-mcp-"));
|
|
485
|
+
const credentialPath = join(dir, "credentials.json");
|
|
486
|
+
const bigPath = join(dir, "big.pdf");
|
|
487
|
+
writeCredentials(
|
|
488
|
+
{
|
|
489
|
+
apiBaseUrl: "https://artifactly.example",
|
|
490
|
+
apiKey: "art_t...et",
|
|
491
|
+
email: "agent@example.com",
|
|
492
|
+
workspace: "agent",
|
|
493
|
+
},
|
|
494
|
+
credentialPath,
|
|
495
|
+
);
|
|
496
|
+
writeFileSync(bigPath, Buffer.alloc(10_000_001));
|
|
497
|
+
|
|
498
|
+
await expect(
|
|
499
|
+
callArtifactlyMcpTool({
|
|
500
|
+
name: "artifactly_publish_file",
|
|
501
|
+
credentialPath,
|
|
502
|
+
arguments: { path: bigPath },
|
|
503
|
+
}),
|
|
504
|
+
).rejects.toThrow(/File is too large.*10 MB/);
|
|
505
|
+
|
|
506
|
+
rmSync(dir, { recursive: true, force: true });
|
|
507
|
+
});
|
|
420
508
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { basename, extname } from "node:path";
|
|
3
3
|
|
|
4
4
|
import {
|
|
@@ -32,6 +32,9 @@ type RequestJson = (
|
|
|
32
32
|
options?: { apiKey?: string },
|
|
33
33
|
) => Promise<unknown>;
|
|
34
34
|
|
|
35
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
36
|
+
const MAX_UPLOAD_BYTES = 10_000_000;
|
|
37
|
+
|
|
35
38
|
function createDefaultRequestJson(apiBaseUrl: string): RequestJson {
|
|
36
39
|
return async (method, path, body, options) => {
|
|
37
40
|
const response = await fetch(`${apiBaseUrl}${path}`, {
|
|
@@ -41,6 +44,7 @@ function createDefaultRequestJson(apiBaseUrl: string): RequestJson {
|
|
|
41
44
|
...(options?.apiKey ? { authorization: `Bearer ${options.apiKey}` } : {}),
|
|
42
45
|
},
|
|
43
46
|
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
47
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
44
48
|
});
|
|
45
49
|
const payload = (await response.json().catch(() => ({}))) as unknown;
|
|
46
50
|
if (!response.ok) {
|
|
@@ -122,6 +126,12 @@ function booleanArg(args: Record<string, unknown> | undefined, key: string): boo
|
|
|
122
126
|
|
|
123
127
|
function readFileForPublish(path: string): Buffer {
|
|
124
128
|
try {
|
|
129
|
+
const stats = statSync(path);
|
|
130
|
+
if (stats.size > MAX_UPLOAD_BYTES) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`File is too large: ${path}. Maximum supported upload size is 10 MB.`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
125
135
|
return readFileSync(path);
|
|
126
136
|
} catch (error) {
|
|
127
137
|
const code = typeof error === "object" && error && "code" in error ? String(error.code) : "";
|
|
@@ -158,6 +168,25 @@ function kindForMime(_path: string, mimeType: string) {
|
|
|
158
168
|
return "file";
|
|
159
169
|
}
|
|
160
170
|
|
|
171
|
+
function queryString(params: Record<string, string | number | undefined>): string {
|
|
172
|
+
const search = new URLSearchParams();
|
|
173
|
+
for (const [key, value] of Object.entries(params)) {
|
|
174
|
+
if (value !== undefined) {
|
|
175
|
+
search.set(key, String(value));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const serialized = search.toString();
|
|
179
|
+
return serialized ? `?${serialized}` : "";
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function numberArg(args: Record<string, unknown> | undefined, key: string): number | undefined {
|
|
183
|
+
const value = args?.[key];
|
|
184
|
+
if (typeof value === "number" && Number.isInteger(value)) {
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
|
|
161
190
|
export function listArtifactlyMcpTools(): ArtifactlyMcpTool[] {
|
|
162
191
|
return [
|
|
163
192
|
{
|
|
@@ -200,14 +229,24 @@ export function listArtifactlyMcpTools(): ArtifactlyMcpTool[] {
|
|
|
200
229
|
title: { type: "string" },
|
|
201
230
|
markdown: { type: "string" },
|
|
202
231
|
visibility: { type: "string", enum: ["public", "unlisted", "private", "gated"] },
|
|
232
|
+
accessList: { type: "array", items: { type: "string" } },
|
|
203
233
|
},
|
|
204
234
|
required: ["title", "markdown"],
|
|
205
235
|
},
|
|
206
236
|
},
|
|
207
237
|
{
|
|
208
238
|
name: "artifactly_list_artifacts",
|
|
209
|
-
description:
|
|
210
|
-
|
|
239
|
+
description:
|
|
240
|
+
"List recent Artifactly artifacts without large content bodies. Supports limit (1-100), cursor, visibility, and kind filters.",
|
|
241
|
+
inputSchema: {
|
|
242
|
+
type: "object",
|
|
243
|
+
properties: {
|
|
244
|
+
limit: { type: "number", minimum: 1, maximum: 100 },
|
|
245
|
+
cursor: { type: "string" },
|
|
246
|
+
visibility: { type: "string", enum: ["public", "unlisted", "private", "gated"] },
|
|
247
|
+
kind: { type: "string", enum: ["markdown", "html", "image", "file", "json", "web_app"] },
|
|
248
|
+
},
|
|
249
|
+
},
|
|
211
250
|
},
|
|
212
251
|
{
|
|
213
252
|
name: "artifactly_get_artifact",
|
|
@@ -248,7 +287,7 @@ export function listArtifactlyMcpTools(): ArtifactlyMcpTool[] {
|
|
|
248
287
|
{
|
|
249
288
|
name: "artifactly_publish_file",
|
|
250
289
|
description:
|
|
251
|
-
"Publish a local PDF, HTML, JSON, Markdown, or other file through the Artifactly API. Defaults to unlisted visibility; explicit mimeType takes precedence over file extension.",
|
|
290
|
+
"Publish a local PDF, HTML, JSON, Markdown, or other file through the Artifactly API. Defaults to unlisted visibility; explicit mimeType takes precedence over file extension. Files larger than 10 MB are rejected before upload.",
|
|
252
291
|
inputSchema: {
|
|
253
292
|
type: "object",
|
|
254
293
|
properties: {
|
|
@@ -344,10 +383,17 @@ export async function callArtifactlyMcpTool(input: CallToolInput) {
|
|
|
344
383
|
const title = requireStringArg(input.arguments, "title");
|
|
345
384
|
const content = requireRawStringArg(input.arguments, "markdown");
|
|
346
385
|
const visibility = stringArg(input.arguments, "visibility") ?? "unlisted";
|
|
386
|
+
const accessList = stringArrayArg(input.arguments, "accessList");
|
|
347
387
|
return objectResult(
|
|
348
388
|
await postJson(
|
|
349
389
|
"/api/artifacts",
|
|
350
|
-
{
|
|
390
|
+
{
|
|
391
|
+
title,
|
|
392
|
+
content,
|
|
393
|
+
kind: "markdown",
|
|
394
|
+
visibility,
|
|
395
|
+
...(accessList ? { accessList } : {}),
|
|
396
|
+
},
|
|
351
397
|
{ apiKey: credentials.apiKey },
|
|
352
398
|
),
|
|
353
399
|
);
|
|
@@ -357,7 +403,12 @@ export async function callArtifactlyMcpTool(input: CallToolInput) {
|
|
|
357
403
|
if (!credentials) {
|
|
358
404
|
throw new Error(LOGIN_GUIDANCE);
|
|
359
405
|
}
|
|
360
|
-
|
|
406
|
+
const limit = numberArg(input.arguments, "limit");
|
|
407
|
+
const cursor = stringArg(input.arguments, "cursor");
|
|
408
|
+
const visibility = stringArg(input.arguments, "visibility");
|
|
409
|
+
const kind = stringArg(input.arguments, "kind");
|
|
410
|
+
const query = queryString({ limit, cursor, visibility, kind });
|
|
411
|
+
return objectResult(await requestJson("GET", `/api/artifacts${query}`, undefined, { apiKey: credentials.apiKey }));
|
|
361
412
|
}
|
|
362
413
|
|
|
363
414
|
if (input.name === "artifactly_get_artifact") {
|