← Files Biological Sequence & Alignment ViewerARCHIVED FILE

src/server-workbench-store.ts

11.9 KB · Sep 30, 2026 · 23:01 UTC

↓ Download file

import { createHash, randomUUID } from "node:crypto";
import {
  chmod,
  lstat,
  mkdir,
  readFile,
  readdir,
  unlink,
  writeFile,
} from "node:fs/promises";
import os from "node:os";
import path from "node:path";

import {
  McpServer,
  ResourceTemplate,
} from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

import { SEQUENCE_VIEWER_LIMITS, utf8ByteLength } from "./runtime-contract";

const ARTIFACT_URI_PREFIX = "viewer-artifact://sequence-viewer/generated";
const MAX_AGE_MS = 30 * 24 * 60 * 60 * 1_000;
const UUID_PATTERN =
  /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;

const artifactMetadataSchema = z
  .object({
    createdAt: z.number().int().nonnegative(),
    format: z.string().min(1).max(100),
    id: z.string().regex(UUID_PATTERN),
    mediaType: z.string().min(1).max(200),
    name: z.string().min(1).max(255),
    sha256: z
      .string()
      .regex(/^[a-f0-9]{64}$/u)
      .optional(),
    size: z.number().int().nonnegative(),
    version: z.literal(1),
  })
  .strict();

const sessionMetadataSchema = z
  .object({
    createdAt: z.number().int().nonnegative(),
    id: z.string().regex(UUID_PATTERN),
    name: z.string().min(1).max(255),
    sha256: z
      .string()
      .regex(/^[a-f0-9]{64}$/u)
      .optional(),
    size: z.number().int().nonnegative(),
    version: z.literal(1),
  })
  .strict();

export type PersistedArtifact = z.infer<typeof artifactMetadataSchema> & {
  resourceUri: string;
  sha256: string;
};

export function createServerWorkbenchStore({
  now = Date.now,
  stateDirectory = defaultStateDirectory(),
}: {
  now?: () => number;
  stateDirectory?: string;
} = {}) {
  const root = path.join(stateDirectory, "workbench-v1");
  const artifactsDirectory = path.join(root, "artifacts");
  const sessionsDirectory = path.join(root, "sessions");

  return {
    async persistArtifact({
      content,
      format,
      mediaType,
      name,
    }: {
      content: string;
      format: string;
      mediaType: string;
      name: string;
    }): Promise<PersistedArtifact> {
      const size = utf8ByteLength(content);
      if (size > SEQUENCE_VIEWER_LIMITS.session.maxArtifactBytes) {
        throw new Error(
          `Generated artifact is ${size.toLocaleString()} bytes; the bounded artifact store accepts at most ${SEQUENCE_VIEWER_LIMITS.session.maxArtifactBytes.toLocaleString()} bytes.`,
        );
      }
      await prepareDirectory(artifactsDirectory);
      const id = randomUUID();
      const metadata = artifactMetadataSchema.parse({
        createdAt: now(),
        format,
        id,
        mediaType,
        name: safeFileName(name),
        sha256: sha256Text(content),
        size,
        version: 1,
      });
      await Promise.all([
        writePrivateFile(artifactContentPath(artifactsDirectory, id), content),
        writePrivateFile(
          artifactMetadataPath(artifactsDirectory, id),
          `${JSON.stringify(metadata)}\n`,
        ),
      ]);
      await pruneDirectory(
        artifactsDirectory,
        ".metadata.json",
        SEQUENCE_VIEWER_LIMITS.session.maxArtifacts,
        now(),
      );
      return {
        ...metadata,
        resourceUri: artifactResourceUri(id),
        sha256: metadata.sha256 as string,
      };
    },

    async readArtifact(id: string): Promise<{
      content: string;
      metadata: z.infer<typeof artifactMetadataSchema>;
    }> {
      assertUuid(id);
      const metadata = artifactMetadataSchema.parse(
        JSON.parse(
          await readBoundedText(
            artifactMetadataPath(artifactsDirectory, id),
            16 * 1_024,
          ),
        ),
      );
      if (now() - metadata.createdAt > MAX_AGE_MS) {
        await deleteArtifactFiles(artifactsDirectory, id);
        throw new Error("This generated viewer artifact has expired.");
      }
      const content = await readBoundedText(
        artifactContentPath(artifactsDirectory, id),
        SEQUENCE_VIEWER_LIMITS.session.maxArtifactBytes,
      );
      if (utf8ByteLength(content) !== metadata.size) {
        throw new Error(
          "Generated viewer artifact metadata does not match its content.",
        );
      }
      if (metadata.sha256 != null && sha256Text(content) !== metadata.sha256) {
        throw new Error(
          "Generated viewer artifact digest does not match its content.",
        );
      }
      return { content, metadata };
    },

    register(server: McpServer): void {
      server.registerResource(
        "generated biological sequence artifact",
        new ResourceTemplate(`${ARTIFACT_URI_PREFIX}/{id}`, {
          list: undefined,
        }),
        {
          description:
            "A bounded derived artifact exported by the Biological Sequence & Alignment Viewer.",
          mimeType: "text/plain",
        },
        async (uri, variables) => {
          const id =
            typeof variables.id === "string" ? variables.id : undefined;
          if (id == null) throw new Error("Generated artifact ID is missing.");
          const { content, metadata } = await this.readArtifact(id);
          return {
            contents: [
              {
                mimeType: metadata.mediaType,
                text: content,
                uri: uri.href,
              },
            ],
          };
        },
      );
    },

    async saveSession({
      name,
      session,
    }: {
      name: string;
      session: string;
    }): Promise<{ id: string; name: string; sha256: string; size: number }> {
      const size = utf8ByteLength(session);
      if (size > SEQUENCE_VIEWER_LIMITS.session.maxSessionBytes) {
        throw new Error(
          `Viewer session is ${size.toLocaleString()} bytes; the bounded session store accepts at most ${SEQUENCE_VIEWER_LIMITS.session.maxSessionBytes.toLocaleString()} bytes.`,
        );
      }
      await prepareDirectory(sessionsDirectory);
      const id = randomUUID();
      const metadata = sessionMetadataSchema.parse({
        createdAt: now(),
        id,
        name: safeFileName(name),
        sha256: sha256Text(session),
        size,
        version: 1,
      });
      await Promise.all([
        writePrivateFile(sessionContentPath(sessionsDirectory, id), session),
        writePrivateFile(
          sessionMetadataPath(sessionsDirectory, id),
          `${JSON.stringify(metadata)}\n`,
        ),
      ]);
      await pruneDirectory(
        sessionsDirectory,
        ".metadata.json",
        SEQUENCE_VIEWER_LIMITS.session.maxSessions,
        now(),
      );
      return {
        id,
        name: metadata.name,
        sha256: metadata.sha256 as string,
        size,
      };
    },

    async readSession(id: string): Promise<string> {
      assertUuid(id);
      const metadata = sessionMetadataSchema.parse(
        JSON.parse(
          await readBoundedText(
            sessionMetadataPath(sessionsDirectory, id),
            16 * 1_024,
          ),
        ),
      );
      if (now() - metadata.createdAt > MAX_AGE_MS) {
        await deleteSessionFiles(sessionsDirectory, id);
        throw new Error("This saved viewer session has expired.");
      }
      const session = await readBoundedText(
        sessionContentPath(sessionsDirectory, id),
        SEQUENCE_VIEWER_LIMITS.session.maxSessionBytes,
      );
      if (utf8ByteLength(session) !== metadata.size) {
        throw new Error(
          "Saved viewer session metadata does not match its content.",
        );
      }
      if (metadata.sha256 != null && sha256Text(session) !== metadata.sha256) {
        throw new Error(
          "Saved viewer session digest does not match its content.",
        );
      }
      return session;
    },
  };
}

function sha256Text(value: string): string {
  return createHash("sha256").update(value, "utf8").digest("hex");
}

function defaultStateDirectory(): string {
  const configuredCodexHome = process.env.CODEX_HOME?.trim();
  return path.resolve(
    configuredCodexHome || path.join(os.homedir(), ".codex"),
    "state",
    "plugins",
    "sequence-viewer",
  );
}

function artifactResourceUri(id: string): string {
  return `${ARTIFACT_URI_PREFIX}/${id}`;
}

async function prepareDirectory(directory: string): Promise<void> {
  await mkdir(directory, { mode: 0o700, recursive: true });
  const stats = await lstat(directory);
  if (!stats.isDirectory() || stats.isSymbolicLink()) {
    throw new Error(
      "Viewer workbench state must be stored in a private directory.",
    );
  }
  if (typeof process.getuid === "function" && stats.uid !== process.getuid()) {
    throw new Error(
      "Viewer workbench state directory is owned by another user.",
    );
  }
  if (process.platform !== "win32") {
    await chmod(directory, 0o700);
  }
}

async function writePrivateFile(
  filePath: string,
  content: string,
): Promise<void> {
  await writeFile(filePath, content, { flag: "wx", mode: 0o600 });
}

async function readBoundedText(
  filePath: string,
  maxBytes: number,
): Promise<string> {
  const stats = await lstat(filePath);
  if (!stats.isFile() || stats.isSymbolicLink() || stats.size > maxBytes) {
    throw new Error(
      "Viewer workbench state file is invalid or exceeds its budget.",
    );
  }
  return readFile(filePath, "utf8");
}

async function pruneDirectory(
  directory: string,
  suffix: string,
  maxItems: number,
  currentTime: number,
): Promise<void> {
  const entries = await readdir(directory, { withFileTypes: true });
  const metadataFiles = (
    await Promise.all(
      entries
        .filter((entry) => entry.isFile() && entry.name.endsWith(suffix))
        .map(async (entry) => {
          const filePath = path.join(directory, entry.name);
          try {
            const stats = await lstat(filePath);
            const id = entry.name.slice(0, -suffix.length);
            return UUID_PATTERN.test(id)
              ? { createdAt: stats.mtimeMs, id }
              : null;
          } catch {
            return null;
          }
        }),
    )
  )
    .filter((value) => value != null)
    .sort((left, right) => right.createdAt - left.createdAt);
  await Promise.all(
    metadataFiles
      .filter(
        ({ createdAt }, index) =>
          index >= maxItems || currentTime - createdAt > MAX_AGE_MS,
      )
      .map(({ id }) =>
        directory.endsWith("artifacts")
          ? deleteArtifactFiles(directory, id)
          : deleteSessionFiles(directory, id),
      ),
  );
}

async function deleteArtifactFiles(
  directory: string,
  id: string,
): Promise<void> {
  await Promise.all([
    unlinkIfExists(artifactContentPath(directory, id)),
    unlinkIfExists(artifactMetadataPath(directory, id)),
  ]);
}

async function deleteSessionFiles(
  directory: string,
  id: string,
): Promise<void> {
  await Promise.all([
    unlinkIfExists(sessionContentPath(directory, id)),
    unlinkIfExists(sessionMetadataPath(directory, id)),
  ]);
}

async function unlinkIfExists(filePath: string): Promise<void> {
  try {
    await unlink(filePath);
  } catch (error) {
    if (
      !(error instanceof Error && "code" in error && error.code === "ENOENT")
    ) {
      throw error;
    }
  }
}

function artifactContentPath(directory: string, id: string): string {
  return path.join(directory, `${id}.artifact`);
}

function artifactMetadataPath(directory: string, id: string): string {
  return path.join(directory, `${id}.metadata.json`);
}

function sessionContentPath(directory: string, id: string): string {
  return path.join(directory, `${id}.session.json`);
}

function sessionMetadataPath(directory: string, id: string): string {
  return path.join(directory, `${id}.metadata.json`);
}

function assertUuid(value: string): void {
  if (!UUID_PATTERN.test(value))
    throw new Error("Viewer workbench identifier is invalid.");
}

function safeFileName(value: string): string {
  const name = path
    .basename(value)
    .replaceAll(/[\0\r\n]/gu, "")
    .slice(0, 255);
  if (name.length === 0 || name === "." || name === "..") {
    throw new Error("Generated artifact name is invalid.");
  }
  return name;
}

SHA-256: e36d28165511f6cb061c0e342ff98ae22c72e702a0dc6431d9bfc08aaf412998