artifacty 0.1.0

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.
@@ -0,0 +1,495 @@
1
+ #!/usr/bin/env node
2
+ import readline from "node:readline";
3
+ import {
4
+ archiveArtifact,
5
+ createArtifact,
6
+ createStore,
7
+ getArtifact,
8
+ listAuditEvents,
9
+ listArtifacts,
10
+ restoreArtifact,
11
+ updateArtifact
12
+ } from "./lib/storage.js";
13
+ import { convertAgentArtifact } from "./lib/converters.js";
14
+ import { resolvePublicBaseUrl } from "./lib/server-state.js";
15
+
16
+ const PROTOCOL_VERSION = "2025-06-18";
17
+ const store = createStore();
18
+
19
+ const nativeArtifactInputSchema = {
20
+ type: "object",
21
+ properties: {
22
+ title: { type: "string", description: "Human-readable artifact title." },
23
+ content: { type: "string", description: "Artifact content." },
24
+ format: {
25
+ type: "string",
26
+ enum: ["html", "markdown", "text", "json"],
27
+ description: "Content format."
28
+ },
29
+ artifactType: {
30
+ type: "string",
31
+ enum: ["document", "html-page", "handoff", "code-review", "test-report", "dashboard", "design-option", "diff-walkthrough", "bundle", "asset", "unknown"]
32
+ },
33
+ schemaVersion: {
34
+ type: "number",
35
+ enum: [1]
36
+ },
37
+ sourceAgent: { type: "string", description: "Agent or tool that produced the artifact." },
38
+ tags: {
39
+ type: "array",
40
+ items: { type: "string" },
41
+ description: "Searchable labels."
42
+ },
43
+ metadata: {
44
+ type: "object",
45
+ description: "Optional JSON metadata."
46
+ },
47
+ allowSecrets: {
48
+ type: "boolean",
49
+ description: "Set true only when intentionally storing content that matches secret patterns."
50
+ }
51
+ },
52
+ required: ["title", "content"]
53
+ };
54
+
55
+ const mutatingArtifactAnnotations = {
56
+ destructiveHint: false,
57
+ idempotentHint: false,
58
+ openWorldHint: false
59
+ };
60
+
61
+ const tools = [
62
+ {
63
+ name: "artifacty_create",
64
+ title: "Create Artifact",
65
+ description: "Create a new Artifacty-native artifact from title, content, format, tags, and metadata.",
66
+ inputSchema: nativeArtifactInputSchema,
67
+ annotations: mutatingArtifactAnnotations
68
+ },
69
+ {
70
+ name: "artifacty_publish",
71
+ title: "Publish Artifact",
72
+ description: "Backwards-compatible alias for artifacty_create.",
73
+ inputSchema: nativeArtifactInputSchema,
74
+ annotations: mutatingArtifactAnnotations
75
+ },
76
+ {
77
+ name: "artifacty_list",
78
+ title: "List Artifacts",
79
+ description: "List artifacts from the local Artifacty store.",
80
+ inputSchema: {
81
+ type: "object",
82
+ properties: {
83
+ query: { type: "string" },
84
+ tag: { type: "string" },
85
+ sourceAgent: { type: "string" },
86
+ includeArchived: { type: "boolean" },
87
+ limit: { type: "number" }
88
+ }
89
+ },
90
+ annotations: {
91
+ readOnlyHint: true,
92
+ openWorldHint: false
93
+ }
94
+ },
95
+ {
96
+ name: "artifacty_import",
97
+ title: "Import Agent Artifact",
98
+ description: "Convert an artifact produced by Claude, Codex, Gemini, or another agent into Artifacty format and save it.",
99
+ inputSchema: {
100
+ type: "object",
101
+ properties: {
102
+ agent: {
103
+ type: "string",
104
+ enum: ["auto", "claude", "codex", "gemini", "artifacty", "generic"],
105
+ description: "Original agent family. Use auto when unsure."
106
+ },
107
+ title: { type: "string", description: "Optional title override." },
108
+ content: { type: "string", description: "Raw artifact file contents or serialized agent payload." },
109
+ payload: { type: "object", description: "Structured agent payload when available." },
110
+ format: {
111
+ type: "string",
112
+ enum: ["html", "markdown", "text", "json"]
113
+ },
114
+ artifactType: {
115
+ type: "string",
116
+ enum: ["document", "html-page", "handoff", "code-review", "test-report", "dashboard", "design-option", "diff-walkthrough", "bundle", "asset", "unknown"]
117
+ },
118
+ schemaVersion: {
119
+ type: "number",
120
+ enum: [1]
121
+ },
122
+ contentType: { type: "string" },
123
+ fileName: { type: "string" },
124
+ sourcePath: { type: "string" },
125
+ tags: {
126
+ type: "array",
127
+ items: { type: "string" }
128
+ },
129
+ metadata: { type: "object" },
130
+ allowSecrets: {
131
+ type: "boolean",
132
+ description: "Set true only when intentionally storing content that matches secret patterns."
133
+ }
134
+ },
135
+ required: []
136
+ },
137
+ annotations: mutatingArtifactAnnotations
138
+ },
139
+ {
140
+ name: "artifacty_get",
141
+ title: "Get Artifact",
142
+ description: "Get artifact metadata and content by ID.",
143
+ inputSchema: {
144
+ type: "object",
145
+ properties: {
146
+ id: { type: "string", description: "Artifact ID." },
147
+ version: { type: "number", description: "Optional version number." },
148
+ includeContent: { type: "boolean", description: "Include content in the response. Defaults to true." }
149
+ },
150
+ required: ["id"]
151
+ },
152
+ annotations: {
153
+ readOnlyHint: true,
154
+ openWorldHint: false
155
+ }
156
+ },
157
+ {
158
+ name: "artifacty_update",
159
+ title: "Update Artifact",
160
+ description: "Append a new version to an existing artifact.",
161
+ inputSchema: {
162
+ type: "object",
163
+ properties: {
164
+ id: { type: "string", description: "Artifact ID." },
165
+ title: { type: "string" },
166
+ content: { type: "string" },
167
+ format: {
168
+ type: "string",
169
+ enum: ["html", "markdown", "text", "json"]
170
+ },
171
+ artifactType: {
172
+ type: "string",
173
+ enum: ["document", "html-page", "handoff", "code-review", "test-report", "dashboard", "design-option", "diff-walkthrough", "bundle", "asset", "unknown"]
174
+ },
175
+ schemaVersion: {
176
+ type: "number",
177
+ enum: [1]
178
+ },
179
+ sourceAgent: { type: "string" },
180
+ tags: {
181
+ type: "array",
182
+ items: { type: "string" }
183
+ },
184
+ metadata: { type: "object" },
185
+ allowSecrets: {
186
+ type: "boolean",
187
+ description: "Set true only when intentionally storing content that matches secret patterns."
188
+ }
189
+ },
190
+ required: ["id", "content"]
191
+ },
192
+ annotations: {
193
+ destructiveHint: false,
194
+ idempotentHint: false,
195
+ openWorldHint: false
196
+ }
197
+ },
198
+ {
199
+ name: "artifacty_archive",
200
+ title: "Archive Artifact",
201
+ description: "Mark an artifact archived without deleting its versions.",
202
+ inputSchema: {
203
+ type: "object",
204
+ properties: {
205
+ id: { type: "string", description: "Artifact ID." }
206
+ },
207
+ required: ["id"]
208
+ },
209
+ annotations: mutatingArtifactAnnotations
210
+ },
211
+ {
212
+ name: "artifacty_restore",
213
+ title: "Restore Artifact",
214
+ description: "Restore a previously archived artifact.",
215
+ inputSchema: {
216
+ type: "object",
217
+ properties: {
218
+ id: { type: "string", description: "Artifact ID." }
219
+ },
220
+ required: ["id"]
221
+ },
222
+ annotations: mutatingArtifactAnnotations
223
+ },
224
+ {
225
+ name: "artifacty_audit",
226
+ title: "List Audit Events",
227
+ description: "List recent audit events for the Artifacty store or a single artifact.",
228
+ inputSchema: {
229
+ type: "object",
230
+ properties: {
231
+ artifactId: { type: "string" },
232
+ limit: { type: "number" }
233
+ }
234
+ },
235
+ annotations: {
236
+ readOnlyHint: true,
237
+ openWorldHint: false
238
+ }
239
+ },
240
+ {
241
+ name: "artifacty_info",
242
+ title: "Artifacty Info",
243
+ description: "Return local Artifacty store and browser URL information.",
244
+ inputSchema: {
245
+ type: "object",
246
+ properties: {}
247
+ },
248
+ annotations: {
249
+ readOnlyHint: true,
250
+ openWorldHint: false
251
+ }
252
+ }
253
+ ];
254
+
255
+ const rl = readline.createInterface({
256
+ input: process.stdin,
257
+ crlfDelay: Infinity
258
+ });
259
+
260
+ rl.on("line", (line) => {
261
+ if (!line.trim()) {
262
+ return;
263
+ }
264
+ handleLine(line).catch((error) => {
265
+ process.stderr.write(`${error.stack || error.message}\n`);
266
+ });
267
+ });
268
+
269
+ async function handleLine(line) {
270
+ let message;
271
+ try {
272
+ message = JSON.parse(line);
273
+ } catch {
274
+ writeResponse(null, undefined, {
275
+ code: -32700,
276
+ message: "Parse error"
277
+ });
278
+ return;
279
+ }
280
+
281
+ if (!message.id && message.id !== 0) {
282
+ await handleNotification(message);
283
+ return;
284
+ }
285
+
286
+ try {
287
+ const result = await handleRequest(message);
288
+ writeResponse(message.id, result);
289
+ } catch (error) {
290
+ writeResponse(message.id, undefined, {
291
+ code: error.jsonRpcCode || -32603,
292
+ message: error.message
293
+ });
294
+ }
295
+ }
296
+
297
+ async function handleNotification(message) {
298
+ if (message.method === "notifications/initialized") {
299
+ return;
300
+ }
301
+ process.stderr.write(`Ignoring MCP notification: ${message.method}\n`);
302
+ }
303
+
304
+ async function handleRequest(message) {
305
+ if (message.method === "initialize") {
306
+ return {
307
+ protocolVersion: PROTOCOL_VERSION,
308
+ capabilities: {
309
+ tools: {
310
+ listChanged: false
311
+ }
312
+ },
313
+ serverInfo: {
314
+ name: "artifacty",
315
+ title: "Artifacty",
316
+ version: "0.1.0"
317
+ },
318
+ instructions: "Use Artifacty to create, import, list, read, and update local artifacts that other agents can reuse."
319
+ };
320
+ }
321
+
322
+ if (message.method === "ping") {
323
+ return {};
324
+ }
325
+
326
+ if (message.method === "tools/list") {
327
+ return { tools };
328
+ }
329
+
330
+ if (message.method === "tools/call") {
331
+ const params = message.params || {};
332
+ return callTool(params.name, params.arguments || {});
333
+ }
334
+
335
+ throw Object.assign(new Error(`Method not found: ${message.method}`), {
336
+ jsonRpcCode: -32601
337
+ });
338
+ }
339
+
340
+ async function callTool(name, args) {
341
+ if (name === "artifacty_create" || name === "artifacty_publish") {
342
+ return toolResult(await withUrls(await createNativeArtifact(args)));
343
+ }
344
+
345
+ if (name === "artifacty_list") {
346
+ const publicBaseUrl = await resolvePublicBaseUrl(store);
347
+ const artifacts = await listArtifacts(store, args);
348
+ return toolResult({
349
+ artifacts: artifacts.map((artifact) => ({
350
+ ...artifact,
351
+ url: `${publicBaseUrl}/artifacts/${encodeURIComponent(artifact.id)}`
352
+ }))
353
+ });
354
+ }
355
+
356
+ if (name === "artifacty_import") {
357
+ const converted = convertAgentArtifact(args);
358
+ const artifact = await createArtifact(store, {
359
+ ...converted,
360
+ allowSecrets: args.allowSecrets,
361
+ auditAction: "import",
362
+ audit: mcpAuditContext()
363
+ });
364
+ return toolResult({
365
+ ...await withUrls(artifact),
366
+ converted
367
+ });
368
+ }
369
+
370
+ if (name === "artifacty_get") {
371
+ const artifact = await getArtifact(store, requireArg(args, "id"), {
372
+ version: args.version,
373
+ audit: mcpAuditContext()
374
+ });
375
+ const decorated = await withUrls(artifact);
376
+ if (args.includeContent === false) {
377
+ delete decorated.content;
378
+ }
379
+ return toolResult(decorated);
380
+ }
381
+
382
+ if (name === "artifacty_update") {
383
+ const artifact = await updateArtifact(store, requireArg(args, "id"), {
384
+ title: args.title,
385
+ content: args.content,
386
+ format: args.format,
387
+ artifactType: args.artifactType,
388
+ schemaVersion: args.schemaVersion,
389
+ sourceAgent: args.sourceAgent || "mcp",
390
+ tags: args.tags || [],
391
+ metadata: args.metadata || {},
392
+ allowSecrets: args.allowSecrets,
393
+ audit: mcpAuditContext()
394
+ });
395
+ return toolResult(await withUrls(artifact));
396
+ }
397
+
398
+ if (name === "artifacty_archive" || name === "artifacty_restore") {
399
+ const artifact = name === "artifacty_archive"
400
+ ? await archiveArtifact(store, requireArg(args, "id"), { audit: mcpAuditContext() })
401
+ : await restoreArtifact(store, requireArg(args, "id"), { audit: mcpAuditContext() });
402
+ return toolResult(await withUrls(artifact));
403
+ }
404
+
405
+ if (name === "artifacty_audit") {
406
+ return toolResult({
407
+ events: await listAuditEvents(store, {
408
+ artifactId: args.artifactId,
409
+ limit: args.limit
410
+ })
411
+ });
412
+ }
413
+
414
+ if (name === "artifacty_info") {
415
+ const publicBaseUrl = await resolvePublicBaseUrl(store);
416
+ return toolResult({
417
+ name: "artifacty",
418
+ store: store.home,
419
+ url: publicBaseUrl,
420
+ mcpProtocolVersion: PROTOCOL_VERSION,
421
+ serverCommand: "node src/mcp-server.js",
422
+ browserCommand: "npm start"
423
+ });
424
+ }
425
+
426
+ throw Object.assign(new Error(`Unknown tool: ${name}`), {
427
+ jsonRpcCode: -32602
428
+ });
429
+ }
430
+
431
+ async function createNativeArtifact(args) {
432
+ return createArtifact(store, {
433
+ title: args.title,
434
+ content: args.content,
435
+ format: args.format,
436
+ artifactType: args.artifactType,
437
+ schemaVersion: args.schemaVersion,
438
+ sourceAgent: args.sourceAgent || "mcp",
439
+ tags: args.tags || [],
440
+ metadata: args.metadata || {},
441
+ allowSecrets: args.allowSecrets,
442
+ audit: mcpAuditContext()
443
+ });
444
+ }
445
+
446
+ function mcpAuditContext() {
447
+ return {
448
+ surface: "mcp",
449
+ actor: "mcp-client"
450
+ };
451
+ }
452
+
453
+ async function withUrls(artifact) {
454
+ const publicBaseUrl = await resolvePublicBaseUrl(store);
455
+ return {
456
+ ...artifact,
457
+ url: `${publicBaseUrl}/artifacts/${encodeURIComponent(artifact.id)}`,
458
+ rawUrl: `${publicBaseUrl}/artifacts/${encodeURIComponent(artifact.id)}/raw?version=${artifact.version.version}`
459
+ };
460
+ }
461
+
462
+ function toolResult(data) {
463
+ return {
464
+ content: [
465
+ {
466
+ type: "text",
467
+ text: JSON.stringify(data, null, 2)
468
+ }
469
+ ],
470
+ structuredContent: data,
471
+ isError: false
472
+ };
473
+ }
474
+
475
+ function requireArg(args, name) {
476
+ if (!args[name]) {
477
+ throw Object.assign(new Error(`Missing required argument: ${name}`), {
478
+ jsonRpcCode: -32602
479
+ });
480
+ }
481
+ return args[name];
482
+ }
483
+
484
+ function writeResponse(id, result, error) {
485
+ const response = {
486
+ jsonrpc: "2.0",
487
+ id
488
+ };
489
+ if (error) {
490
+ response.error = error;
491
+ } else {
492
+ response.result = result;
493
+ }
494
+ process.stdout.write(`${JSON.stringify(response)}\n`);
495
+ }