artifacty 0.7.0 → 0.9.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.
- package/README.md +21 -0
- package/docs/central-team-deployment-design.md +208 -0
- package/docs/integrations.md +32 -1
- package/docs/mcp-public-api.md +13 -4
- package/docs/network-sharing.md +13 -0
- package/docs/release-checklist.md +7 -0
- package/docs/threat-model.md +20 -6
- package/package.json +2 -1
- package/src/cli.js +37 -6
- package/src/lib/background.js +3 -0
- package/src/lib/doctor.js +214 -0
- package/src/lib/installer.js +31 -0
- package/src/lib/render.js +250 -1
- package/src/lib/security.js +4 -0
- package/src/lib/service.js +4 -0
- package/src/lib/storage.js +332 -1
- package/src/mcp-server.js +225 -81
- package/src/server.js +317 -13
package/src/server.js
CHANGED
|
@@ -6,29 +6,45 @@ import { URL } from "node:url";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import {
|
|
8
8
|
archiveArtifact,
|
|
9
|
+
authenticateApiToken,
|
|
10
|
+
countUsers,
|
|
11
|
+
createApiToken,
|
|
9
12
|
createArtifact,
|
|
13
|
+
createSession,
|
|
10
14
|
createStore,
|
|
15
|
+
createUser,
|
|
11
16
|
getArtifact,
|
|
17
|
+
getSessionUser,
|
|
18
|
+
listApiTokens,
|
|
12
19
|
listArtifactsPage,
|
|
13
20
|
listAuditEvents,
|
|
21
|
+
listUsers,
|
|
14
22
|
MAX_ARTIFACT_BYTES,
|
|
23
|
+
revokeApiToken,
|
|
24
|
+
revokeSession,
|
|
15
25
|
restoreArtifact,
|
|
16
|
-
|
|
26
|
+
setUserActive,
|
|
27
|
+
updateArtifact,
|
|
28
|
+
verifyUserPassword
|
|
17
29
|
} from "./lib/storage.js";
|
|
18
30
|
import { convertAgentArtifact } from "./lib/converters.js";
|
|
19
31
|
import { createLineDiff } from "./lib/diff.js";
|
|
20
32
|
import { EDITOR_CLIENT_PATH, VIEWER_CLIENT_PATH, editorClientFilePath, editorVendorPath, viewerClientFilePath } from "./lib/editor-assets.js";
|
|
21
33
|
import { localeFromBodyOrUrl, localeFromUrl, localizedHref } from "./lib/i18n.js";
|
|
22
|
-
import { exposureWarning,
|
|
34
|
+
import { exposureWarning, requestToken, securityConfig, tokensEqual, validateServerExposure } from "./lib/security.js";
|
|
23
35
|
import { writeServerState } from "./lib/server-state.js";
|
|
24
36
|
import { generateToken } from "./lib/token.js";
|
|
37
|
+
import { createMcpJsonRpcHandler } from "./mcp-server.js";
|
|
25
38
|
import {
|
|
26
39
|
renderArtifactFormPage,
|
|
27
40
|
renderArtifactPage,
|
|
41
|
+
renderAccountPage,
|
|
42
|
+
renderAdminUsersPage,
|
|
28
43
|
renderReactFramePage,
|
|
29
44
|
renderDashboard,
|
|
30
45
|
renderDiffPage,
|
|
31
46
|
renderImportArtifactPage,
|
|
47
|
+
renderLoginPage,
|
|
32
48
|
renderNewArtifactPage
|
|
33
49
|
} from "./lib/render.js";
|
|
34
50
|
|
|
@@ -46,6 +62,7 @@ export async function startServer(options = {}) {
|
|
|
46
62
|
(!explicitPort && options.portFallback !== false);
|
|
47
63
|
const store = createStore({ home: options.home });
|
|
48
64
|
const security = securityConfig(options);
|
|
65
|
+
const mcpHttp = Boolean(options.mcpHttp || process.env.ARTIFACTY_MCP_HTTP === "true");
|
|
49
66
|
validateServerExposure({ host, config: security });
|
|
50
67
|
|
|
51
68
|
const { server, actualPort } = await listenWithFallback({
|
|
@@ -54,7 +71,7 @@ export async function startServer(options = {}) {
|
|
|
54
71
|
allowPortFallback,
|
|
55
72
|
createServer(port) {
|
|
56
73
|
const candidateServer = http.createServer((request, response) => {
|
|
57
|
-
handleRequest({ request, response, store, host, port: candidateServer.address()?.port || port, security }).catch((error) => {
|
|
74
|
+
handleRequest({ request, response, store, host, port: candidateServer.address()?.port || port, security, mcpHttp }).catch((error) => {
|
|
58
75
|
sendError(response, error);
|
|
59
76
|
});
|
|
60
77
|
});
|
|
@@ -138,7 +155,7 @@ function closeServer(server) {
|
|
|
138
155
|
});
|
|
139
156
|
}
|
|
140
157
|
|
|
141
|
-
export async function handleRequest({ request, response, store, host, port, security = securityConfig() }) {
|
|
158
|
+
export async function handleRequest({ request, response, store, host, port, security = securityConfig(), mcpHttp = false }) {
|
|
142
159
|
const url = new URL(request.url, `http://${request.headers.host || `${host}:${port}`}`);
|
|
143
160
|
const pathname = decodeURIComponent(url.pathname);
|
|
144
161
|
const baseUrl = `http://${host}:${port}`;
|
|
@@ -165,8 +182,156 @@ export async function handleRequest({ request, response, store, host, port, secu
|
|
|
165
182
|
return sendJavaScriptFile(response, vendorPath, headOnly, request);
|
|
166
183
|
}
|
|
167
184
|
|
|
185
|
+
const userCount = await countUsers(store);
|
|
186
|
+
const currentUser = userCount > 0 ? await sessionUserFromRequest(store, request) : null;
|
|
187
|
+
|
|
188
|
+
if (method === "GET" && pathname === "/login") {
|
|
189
|
+
return sendHtml(response, renderLoginPage({
|
|
190
|
+
baseUrl,
|
|
191
|
+
setup: userCount === 0,
|
|
192
|
+
locale,
|
|
193
|
+
currentPath
|
|
194
|
+
}), 200, headOnly);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (method === "POST" && pathname === "/login") {
|
|
198
|
+
const body = await readFormBody(request);
|
|
199
|
+
const email = body.email;
|
|
200
|
+
const password = body.password;
|
|
201
|
+
const user = userCount === 0
|
|
202
|
+
? await createUser(store, {
|
|
203
|
+
email,
|
|
204
|
+
name: body.name || email,
|
|
205
|
+
role: "admin",
|
|
206
|
+
password
|
|
207
|
+
})
|
|
208
|
+
: await verifyUserPassword(store, email, password);
|
|
209
|
+
if (!user) {
|
|
210
|
+
return sendHtml(response, renderLoginPage({
|
|
211
|
+
baseUrl,
|
|
212
|
+
setup: false,
|
|
213
|
+
error: "Invalid email or password.",
|
|
214
|
+
locale,
|
|
215
|
+
currentPath
|
|
216
|
+
}), 401);
|
|
217
|
+
}
|
|
218
|
+
const session = await createSession(store, user.id);
|
|
219
|
+
return sendRedirect(response, "/account", {
|
|
220
|
+
"set-cookie": sessionCookie(session.token)
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (method === "POST" && pathname === "/logout") {
|
|
225
|
+
await revokeSession(store, sessionTokenFromRequest(request));
|
|
226
|
+
return sendRedirect(response, "/login", {
|
|
227
|
+
"set-cookie": clearSessionCookie()
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (method === "GET" && pathname === "/account") {
|
|
232
|
+
if (!currentUser) {
|
|
233
|
+
return sendRedirect(response, "/login");
|
|
234
|
+
}
|
|
235
|
+
const tokens = await listApiTokens(store, currentUser.id);
|
|
236
|
+
return sendHtml(response, renderAccountPage({
|
|
237
|
+
baseUrl,
|
|
238
|
+
user: currentUser,
|
|
239
|
+
tokens,
|
|
240
|
+
locale,
|
|
241
|
+
currentPath
|
|
242
|
+
}), 200, headOnly);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (method === "POST" && pathname === "/account/tokens") {
|
|
246
|
+
if (!currentUser) {
|
|
247
|
+
return sendRedirect(response, "/login");
|
|
248
|
+
}
|
|
249
|
+
const body = await readFormBody(request);
|
|
250
|
+
const created = await createApiToken(store, currentUser.id, {
|
|
251
|
+
name: body.name
|
|
252
|
+
});
|
|
253
|
+
const tokens = await listApiTokens(store, currentUser.id);
|
|
254
|
+
return sendHtml(response, renderAccountPage({
|
|
255
|
+
baseUrl,
|
|
256
|
+
user: currentUser,
|
|
257
|
+
tokens,
|
|
258
|
+
createdToken: created.token,
|
|
259
|
+
locale,
|
|
260
|
+
currentPath: "/account"
|
|
261
|
+
}));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const tokenRevokeMatch = /^\/account\/tokens\/([^/]+)\/revoke$/.exec(pathname);
|
|
265
|
+
if (tokenRevokeMatch && method === "POST") {
|
|
266
|
+
if (!currentUser) {
|
|
267
|
+
return sendRedirect(response, "/login");
|
|
268
|
+
}
|
|
269
|
+
await revokeApiToken(store, tokenRevokeMatch[1], currentUser.id);
|
|
270
|
+
return sendRedirect(response, "/account");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (method === "GET" && pathname === "/admin/users") {
|
|
274
|
+
if (!currentUser) {
|
|
275
|
+
return sendRedirect(response, "/login");
|
|
276
|
+
}
|
|
277
|
+
requireAdmin(currentUser);
|
|
278
|
+
return sendHtml(response, renderAdminUsersPage({
|
|
279
|
+
baseUrl,
|
|
280
|
+
user: currentUser,
|
|
281
|
+
users: await listUsers(store),
|
|
282
|
+
locale,
|
|
283
|
+
currentPath
|
|
284
|
+
}), 200, headOnly);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (method === "POST" && pathname === "/admin/users") {
|
|
288
|
+
if (!currentUser) {
|
|
289
|
+
return sendRedirect(response, "/login");
|
|
290
|
+
}
|
|
291
|
+
requireAdmin(currentUser);
|
|
292
|
+
const body = await readFormBody(request);
|
|
293
|
+
await createUser(store, {
|
|
294
|
+
email: body.email,
|
|
295
|
+
name: body.name || body.email,
|
|
296
|
+
role: body.role || "user",
|
|
297
|
+
password: body.password
|
|
298
|
+
});
|
|
299
|
+
return sendRedirect(response, "/admin/users");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const userActiveMatch = /^\/admin\/users\/([^/]+)\/(enable|disable)$/.exec(pathname);
|
|
303
|
+
if (userActiveMatch && method === "POST") {
|
|
304
|
+
if (!currentUser) {
|
|
305
|
+
return sendRedirect(response, "/login");
|
|
306
|
+
}
|
|
307
|
+
requireAdmin(currentUser);
|
|
308
|
+
if (userActiveMatch[1] === currentUser.id && userActiveMatch[2] === "disable") {
|
|
309
|
+
throw Object.assign(new Error("Admins cannot disable their own account"), {
|
|
310
|
+
statusCode: 400,
|
|
311
|
+
code: "SELF_DISABLE_BLOCKED"
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
await setUserActive(store, userActiveMatch[1], userActiveMatch[2] === "enable");
|
|
315
|
+
return sendRedirect(response, "/admin/users");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (pathname === "/mcp") {
|
|
319
|
+
if (!mcpHttp) {
|
|
320
|
+
return sendJson(response, { error: "Not found" }, 404, headOnly);
|
|
321
|
+
}
|
|
322
|
+
request.artifactyAuth = await requireRequestAuth({ store, request, url, config: security });
|
|
323
|
+
return handleMcpHttpRequest({
|
|
324
|
+
request,
|
|
325
|
+
response,
|
|
326
|
+
store,
|
|
327
|
+
baseUrl,
|
|
328
|
+
headOnly,
|
|
329
|
+
method
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
168
333
|
if (pathname.startsWith("/api/")) {
|
|
169
|
-
|
|
334
|
+
request.artifactyAuth = await requireRequestAuth({ store, request, url, config: security });
|
|
170
335
|
}
|
|
171
336
|
|
|
172
337
|
if (method === "GET" && pathname === "/health") {
|
|
@@ -190,7 +355,7 @@ export async function handleRequest({ request, response, store, host, port, secu
|
|
|
190
355
|
limit: filters.limit,
|
|
191
356
|
offset: filters.offset
|
|
192
357
|
});
|
|
193
|
-
return sendHtml(response, renderDashboard({ artifacts: page.artifacts, baseUrl, filters, pagination: page, locale, currentPath }), 200, headOnly);
|
|
358
|
+
return sendHtml(response, renderDashboard({ artifacts: page.artifacts, baseUrl, filters, pagination: page, locale, currentPath, user: currentUser }), 200, headOnly);
|
|
194
359
|
}
|
|
195
360
|
|
|
196
361
|
if (method === "GET" && pathname === "/new") {
|
|
@@ -201,7 +366,7 @@ export async function handleRequest({ request, response, store, host, port, secu
|
|
|
201
366
|
assertLocalOrigin(request);
|
|
202
367
|
const body = await readFormBody(request);
|
|
203
368
|
const bodyLocale = localeFromBodyOrUrl(body, url);
|
|
204
|
-
|
|
369
|
+
request.artifactyAuth = await requireBrowserWriteAuth({ store, request, url, body, config: security, currentUser });
|
|
205
370
|
const artifact = await createArtifact(store, {
|
|
206
371
|
title: body.title,
|
|
207
372
|
content: body.content,
|
|
@@ -225,7 +390,7 @@ export async function handleRequest({ request, response, store, host, port, secu
|
|
|
225
390
|
assertLocalOrigin(request);
|
|
226
391
|
const body = await readFormBody(request);
|
|
227
392
|
const bodyLocale = localeFromBodyOrUrl(body, url);
|
|
228
|
-
|
|
393
|
+
request.artifactyAuth = await requireBrowserWriteAuth({ store, request, url, body, config: security, currentUser });
|
|
229
394
|
const converted = convertAgentArtifact({
|
|
230
395
|
agent: body.agent,
|
|
231
396
|
title: body.title,
|
|
@@ -314,7 +479,7 @@ export async function handleRequest({ request, response, store, host, port, secu
|
|
|
314
479
|
assertLocalOrigin(request);
|
|
315
480
|
const body = await readFormBody(request);
|
|
316
481
|
const bodyLocale = localeFromBodyOrUrl(body, url);
|
|
317
|
-
|
|
482
|
+
request.artifactyAuth = await requireBrowserWriteAuth({ store, request, url, body, config: security, currentUser });
|
|
318
483
|
const artifact = await updateArtifact(store, editMatch[1], {
|
|
319
484
|
title: body.title,
|
|
320
485
|
content: body.content,
|
|
@@ -357,7 +522,7 @@ export async function handleRequest({ request, response, store, host, port, secu
|
|
|
357
522
|
assertLocalOrigin(request);
|
|
358
523
|
const body = await readFormBody(request);
|
|
359
524
|
const bodyLocale = localeFromBodyOrUrl(body, url);
|
|
360
|
-
|
|
525
|
+
request.artifactyAuth = await requireBrowserWriteAuth({ store, request, url, body, config: security, currentUser });
|
|
361
526
|
const artifact = archiveMatch[2] === "archive"
|
|
362
527
|
? await archiveArtifact(store, archiveMatch[1], { audit: auditContext(request, "web") })
|
|
363
528
|
: await restoreArtifact(store, archiveMatch[1], { audit: auditContext(request, "web") });
|
|
@@ -452,6 +617,56 @@ export function decorateArtifactUrls(artifact, baseUrl) {
|
|
|
452
617
|
};
|
|
453
618
|
}
|
|
454
619
|
|
|
620
|
+
async function handleMcpHttpRequest({ request, response, store, baseUrl, headOnly, method }) {
|
|
621
|
+
if (method === "OPTIONS") {
|
|
622
|
+
response.writeHead(204, {
|
|
623
|
+
allow: "POST, OPTIONS",
|
|
624
|
+
"cache-control": "no-store"
|
|
625
|
+
});
|
|
626
|
+
response.end();
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (method !== "POST") {
|
|
631
|
+
response.writeHead(405, {
|
|
632
|
+
allow: "POST, OPTIONS",
|
|
633
|
+
"content-type": "application/json; charset=utf-8",
|
|
634
|
+
"cache-control": "no-store",
|
|
635
|
+
"x-content-type-options": "nosniff"
|
|
636
|
+
});
|
|
637
|
+
response.end(headOnly ? undefined : `${JSON.stringify({ error: "MCP endpoint accepts POST JSON-RPC requests" }, null, 2)}\n`);
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const body = await readJsonBody(request);
|
|
642
|
+
const messages = Array.isArray(body) ? body : [body];
|
|
643
|
+
const handler = createMcpJsonRpcHandler({
|
|
644
|
+
store,
|
|
645
|
+
transport: "streamable-http",
|
|
646
|
+
publicBaseUrl: baseUrl,
|
|
647
|
+
serverCommand: `${baseUrl}/mcp`,
|
|
648
|
+
browserCommand: baseUrl,
|
|
649
|
+
auditContext: () => auditContext(request, "mcp-http")
|
|
650
|
+
});
|
|
651
|
+
const responses = [];
|
|
652
|
+
for (const message of messages) {
|
|
653
|
+
const jsonRpcResponse = await handler(message);
|
|
654
|
+
if (jsonRpcResponse) {
|
|
655
|
+
responses.push(jsonRpcResponse);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (responses.length === 0) {
|
|
660
|
+
response.writeHead(202, {
|
|
661
|
+
"cache-control": "no-store"
|
|
662
|
+
});
|
|
663
|
+
response.end();
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
sendJson(response, Array.isArray(body) ? responses : responses[0], 200, headOnly);
|
|
668
|
+
}
|
|
669
|
+
|
|
455
670
|
function paginationJson(page) {
|
|
456
671
|
return {
|
|
457
672
|
total: page.total,
|
|
@@ -622,10 +837,11 @@ function javascriptCorsHeaders(request) {
|
|
|
622
837
|
};
|
|
623
838
|
}
|
|
624
839
|
|
|
625
|
-
export function sendRedirect(response, location) {
|
|
840
|
+
export function sendRedirect(response, location, headers = {}) {
|
|
626
841
|
response.writeHead(303, {
|
|
627
842
|
location,
|
|
628
|
-
"cache-control": "no-store"
|
|
843
|
+
"cache-control": "no-store",
|
|
844
|
+
...headers
|
|
629
845
|
});
|
|
630
846
|
response.end();
|
|
631
847
|
}
|
|
@@ -653,12 +869,98 @@ function splitTags(value) {
|
|
|
653
869
|
}
|
|
654
870
|
|
|
655
871
|
function auditContext(request, surface) {
|
|
872
|
+
const auth = request.artifactyAuth;
|
|
656
873
|
return {
|
|
657
874
|
surface,
|
|
658
|
-
actor: request.headers["x-artifacty-actor"] || request.headers["user-agent"] || "unknown"
|
|
875
|
+
actor: auth?.actor || request.headers["x-artifacty-actor"] || request.headers["user-agent"] || "unknown",
|
|
876
|
+
userId: auth?.user?.id || null,
|
|
877
|
+
tokenId: auth?.tokenId || null
|
|
659
878
|
};
|
|
660
879
|
}
|
|
661
880
|
|
|
881
|
+
async function requireRequestAuth({ store, request, url, body = {}, config }) {
|
|
882
|
+
const token = requestToken({ request, url, body });
|
|
883
|
+
if (config.apiToken && tokensEqual(token, config.apiToken)) {
|
|
884
|
+
return {
|
|
885
|
+
type: "global-token",
|
|
886
|
+
actor: request.headers["x-artifacty-actor"] || "artifacty-token",
|
|
887
|
+
role: "admin"
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
const tokenAuth = await authenticateApiToken(store, token);
|
|
892
|
+
if (tokenAuth) {
|
|
893
|
+
return tokenAuth;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const authRequired = Boolean(config.apiToken) || await countUsers(store) > 0;
|
|
897
|
+
if (!authRequired) {
|
|
898
|
+
return {
|
|
899
|
+
type: "anonymous",
|
|
900
|
+
actor: request.headers["x-artifacty-actor"] || request.headers["user-agent"] || "anonymous"
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
throw Object.assign(new Error("Artifacty authentication required"), {
|
|
905
|
+
code: "AUTH_REQUIRED",
|
|
906
|
+
statusCode: 401
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
async function requireBrowserWriteAuth({ store, request, url, body, config, currentUser }) {
|
|
911
|
+
if (currentUser) {
|
|
912
|
+
return {
|
|
913
|
+
type: "session",
|
|
914
|
+
actor: currentUser.email,
|
|
915
|
+
user: currentUser,
|
|
916
|
+
role: currentUser.role
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
return requireRequestAuth({ store, request, url, body, config });
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function requireAdmin(user) {
|
|
923
|
+
if (user?.role !== "admin") {
|
|
924
|
+
throw Object.assign(new Error("Artifacty admin privileges required"), {
|
|
925
|
+
code: "ADMIN_REQUIRED",
|
|
926
|
+
statusCode: 403
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
async function sessionUserFromRequest(store, request) {
|
|
932
|
+
return getSessionUser(store, sessionTokenFromRequest(request));
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function sessionTokenFromRequest(request) {
|
|
936
|
+
const cookies = parseCookies(request.headers.cookie || "");
|
|
937
|
+
return cookies.artifacty_session || "";
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function parseCookies(header) {
|
|
941
|
+
const cookies = {};
|
|
942
|
+
for (const part of String(header || "").split(";")) {
|
|
943
|
+
const index = part.indexOf("=");
|
|
944
|
+
if (index === -1) {
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
const key = part.slice(0, index).trim();
|
|
948
|
+
const value = part.slice(index + 1).trim();
|
|
949
|
+
if (key) {
|
|
950
|
+
cookies[key] = decodeURIComponent(value);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return cookies;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
function sessionCookie(token) {
|
|
957
|
+
return `artifacty_session=${encodeURIComponent(token)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${7 * 24 * 60 * 60}`;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function clearSessionCookie() {
|
|
961
|
+
return "artifacty_session=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0";
|
|
962
|
+
}
|
|
963
|
+
|
|
662
964
|
function isMain(metaUrl) {
|
|
663
965
|
return process.argv[1] && metaUrl === new URL(`file://${process.argv[1]}`).href;
|
|
664
966
|
}
|
|
@@ -712,6 +1014,8 @@ function parseServerArgs(args) {
|
|
|
712
1014
|
options.generateToken = true;
|
|
713
1015
|
} else if (arg === "--allow-secrets") {
|
|
714
1016
|
options.allowSecrets = true;
|
|
1017
|
+
} else if (arg === "--mcp-http") {
|
|
1018
|
+
options.mcpHttp = true;
|
|
715
1019
|
}
|
|
716
1020
|
}
|
|
717
1021
|
return options;
|