← Files VeraARCHIVED FILE

modules/archive-organization/scripts/review_mcp_server.cjs

24 KB · Oct 2, 2026 · 00:29 UTC

↓ Download file

#!/usr/bin/env node
"use strict";

const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const readline = require("node:readline");
const { spawnSync } = require("node:child_process");
const crypto = require("node:crypto");

const PLUGIN_ROOT = path.resolve(__dirname, "..");
const MANIFEST = JSON.parse(
  fs.readFileSync(path.join(PLUGIN_ROOT, ".codex-plugin", "plugin.json"), "utf8"),
);
const SERVER_NAME = "vera-archive-organization";
const SERVER_VERSION = MANIFEST.version || "0.1.0";
const CLI_PATH = path.join(PLUGIN_ROOT, "scripts", "archive_organization.py");
const WIDGET_URI = "ui://widget/archive-organization-review.html";
const WIDGET_MIME_TYPE = "text/html;profile=mcp-app";
const MAX_ITEMS = 5000;
const MAX_PAYLOAD_BYTES = 8_000_000;
const REVIEW_REFERENCE_TTL_MS = 4 * 60 * 60 * 1000;
const MAX_REVIEW_REFERENCES = 128;
const REVIEW_REFERENCES = new Map();
const ALLOWED_ACTIONS = new Set([
  "accept",
  "reject",
  "edit",
  "mark_unclear",
  "skip",
]);
const ITEM_TYPES = new Set([
  "archive_file_proposal",
  "exact_duplicate_proposal",
]);
const TOOL_NAMES = {
  validateReview: "validate_archive_organization_review",
  renderReview: "render_archive_organization_review",
  saveDecisions: "save_archive_organization_decisions",
  applyDecisions: "apply_archive_organization_decisions",
};

function isPlainObject(value) {
  return value != null && typeof value === "object" && !Array.isArray(value);
}

function objectSchema(properties, required = [], additionalProperties = true) {
  return { type: "object", properties, required, additionalProperties };
}

function icon() {
  const bytes = fs.readFileSync(path.join(PLUGIN_ROOT, "assets", "icon.svg"));
  return {
    src: `data:image/svg+xml;base64,${bytes.toString("base64")}`,
    mimeType: "image/svg+xml",
    sizes: ["24x24"],
  };
}

function toolUiMeta(resourceUri, toolName = null) {
  const meta = {
    ui: { resourceUri, visibility: ["model"] },
    "ui/resourceUri": resourceUri,
    "openai/outputTemplate": resourceUri,
    "openai/widgetAccessible": true,
  };
  if (toolName === TOOL_NAMES.renderReview) {
    meta["openai/toolInvocation/invoking"] = "Rendering archive organization review";
    meta["openai/toolInvocation/invoked"] = "Rendered archive organization review";
  }
  return meta;
}

function reviewPayloadSchema() {
  return objectSchema(
    {
      schema_version: { type: "string" },
      plugin: { type: "string" },
      workflow: { type: "string" },
      run_id: { type: "string" },
      review_type: { type: "string" },
      items: { type: "array", maxItems: MAX_ITEMS, items: { type: "object" } },
      item_count: { type: "number" },
      status: { type: "string" },
      content_sha256: { type: "string" },
    },
    ["schema_version", "plugin", "workflow", "run_id", "items", "item_count"],
  );
}

function decisionSchema() {
  return objectSchema(
    {
      item_id: { type: "string" },
      action: { type: "string", enum: Array.from(ALLOWED_ACTIONS) },
      reviewer_note: { type: "string" },
      edit_value: {
        type: "string",
        description: "Required client-relative destination path for edit.",
      },
      requested_documents: { type: "array", items: { type: "string" } },
    },
    ["item_id", "action"],
  );
}

function toolDefinitions() {
  const validateInput = objectSchema(
    {
      client_engagement: {
        type: "string",
        description:
          "Absolute path to the current Studio Archive context.json. Used once to bind a short-lived opaque review reference.",
      },
      review_payload: reviewPayloadSchema(),
    },
    ["review_payload"],
    false,
  );
  const renderInput = objectSchema(
    {
      review_reference: {
        type: "string",
        pattern: "^archive_review_[0-9a-f]{32}$",
      },
    },
    ["review_reference"],
    false,
  );
  const decisionInput = objectSchema(
    {
      review_reference: {
        type: "string",
        pattern: "^archive_review_[0-9a-f]{32}$",
      },
      decisions: { type: "array", maxItems: MAX_ITEMS, items: decisionSchema() },
      decision_source: { type: "string" },
      reviewer: { type: "string" },
    },
    ["review_reference", "decisions", "reviewer"],
    false,
  );
  return [
    {
      name: TOOL_NAMES.validateReview,
      title: "Validate archive organization review",
      description:
        "Validate one dry-run archive organization payload before rendering it. This never changes client files.",
      inputSchema: validateInput,
      annotations: {
        readOnlyHint: true,
        destructiveHint: false,
        idempotentHint: true,
        openWorldHint: false,
      },
    },
    {
      name: TOOL_NAMES.renderReview,
      title: "Render archive organization review",
      description:
        "Render searchable file, destination, duplicate, anomaly, and confidence rows for collaborator review.",
      inputSchema: renderInput,
      _meta: toolUiMeta(WIDGET_URI, TOOL_NAMES.renderReview),
      annotations: {
        readOnlyHint: true,
        destructiveHint: false,
        idempotentHint: true,
        openWorldHint: false,
      },
    },
    {
      name: TOOL_NAMES.saveDecisions,
      title: "Save archive organization decisions",
      description:
        "Persist validated collaborator decisions to ui_decisions.json. This still does not move client files.",
      inputSchema: decisionInput,
      annotations: {
        readOnlyHint: false,
        destructiveHint: true,
        idempotentHint: true,
        openWorldHint: false,
      },
    },
    {
      name: TOOL_NAMES.applyDecisions,
      title: "Apply archive organization review decisions",
      description:
        "Compile persisted review decisions into approved_plan.json. This does not execute filesystem moves; a separate explicit apply approval remains mandatory.",
      inputSchema: decisionInput,
      annotations: {
        readOnlyHint: false,
        destructiveHint: true,
        idempotentHint: true,
        openWorldHint: false,
      },
    },
  ];
}

function resources() {
  return [
    {
      uri: WIDGET_URI,
      name: "archive_organization_review_widget",
      title: "Archive organization review widget",
      description:
        "Reviews source paths, proposed destinations, duplicate evidence, anomalies, confidence, and collaborator actions.",
      mimeType: WIDGET_MIME_TYPE,
      _meta: {
        ui: { resourceUri: WIDGET_URI },
        "openai/widgetDescription":
          "Interactive review of a dry-run client-folder organization plan. Saving or applying decisions never performs filesystem moves.",
        "openai/widgetPrefersBorder": false,
        "openai/widgetCSP": { connect_domains: [], resource_domains: [] },
        "openai/widgetDomain": "https://chatgpt.com",
      },
    },
  ];
}

function resourceText(uri) {
  if (uri !== WIDGET_URI) throw new Error(`unknown widget resource: ${uri}`);
  return fs.readFileSync(
    path.join(PLUGIN_ROOT, "assets", "archive-organization-review-widget.html"),
    "utf8",
  );
}

function requireString(value, label, maximum = 4096) {
  if (typeof value !== "string" || !value.trim() || value.length > maximum) {
    throw new Error(`${label} must be a bounded non-empty string`);
  }
  return value.trim();
}

function validateReviewPayload(value) {
  if (!isPlainObject(value)) throw new Error("review_payload must be an object");
  if (value.plugin !== "archive-organization" || value.workflow !== "archive-organization") {
    throw new Error("review_payload plugin and workflow must be archive-organization");
  }
  requireString(value.run_id, "review_payload.run_id", 120);
  if (!Array.isArray(value.items) || value.items.length > MAX_ITEMS) {
    throw new Error(`review_payload.items exceeds ${MAX_ITEMS} items`);
  }
  if (value.item_count !== value.items.length) {
    throw new Error("review_payload.item_count is stale");
  }
  const itemIds = new Set();
  for (const item of value.items) {
    if (!isPlainObject(item)) throw new Error("review item must be an object");
    const itemId = requireString(item.id, "review item id", 120);
    if (itemIds.has(itemId)) throw new Error(`duplicate review item id: ${itemId}`);
    itemIds.add(itemId);
    if (!ITEM_TYPES.has(item.item_type)) {
      throw new Error(`review item ${itemId} has unsupported item_type: ${item.item_type}`);
    }
    if (!Array.isArray(item.allowed_actions) || !item.allowed_actions.length) {
      throw new Error(`review item ${itemId} has no allowed actions`);
    }
    for (const action of item.allowed_actions) {
      if (!ALLOWED_ACTIONS.has(action)) {
        throw new Error(`review item ${itemId} has unsupported action: ${action}`);
      }
    }
  }
  if (Buffer.byteLength(JSON.stringify(value), "utf8") > MAX_PAYLOAD_BYTES) {
    throw new Error(`review payload exceeds ${MAX_PAYLOAD_BYTES} bytes`);
  }
  return { itemIds, itemById: new Map(value.items.map((item) => [item.id, item])) };
}

function canonicalValue(value) {
  if (Array.isArray(value)) return value.map(canonicalValue);
  if (!isPlainObject(value)) return value;
  return Object.fromEntries(
    Object.keys(value)
      .sort()
      .map((key) => [key, canonicalValue(value[key])]),
  );
}

function canonicalJson(value) {
  return JSON.stringify(canonicalValue(value));
}

function pruneReviewReferences(now = Date.now()) {
  for (const [reference, context] of REVIEW_REFERENCES.entries()) {
    if (context.expiresAt <= now) REVIEW_REFERENCES.delete(reference);
  }
  while (REVIEW_REFERENCES.size >= MAX_REVIEW_REFERENCES) {
    const oldest = REVIEW_REFERENCES.keys().next().value;
    if (oldest == null) break;
    REVIEW_REFERENCES.delete(oldest);
  }
}

function readRegularJson(filePath, label) {
  const observed = fs.lstatSync(filePath);
  if (!observed.isFile() || observed.isSymbolicLink()) {
    throw new Error(`${label} must be a regular non-symlink file`);
  }
  const bytes = fs.readFileSync(filePath);
  if (bytes.length > MAX_PAYLOAD_BYTES) {
    throw new Error(`${label} exceeds the bounded payload size`);
  }
  let payload;
  try {
    payload = JSON.parse(bytes.toString("utf8"));
  } catch {
    throw new Error(`${label} must contain valid JSON`);
  }
  if (!isPlainObject(payload)) throw new Error(`${label} must contain an object`);
  return { payload, bytes };
}

function registerReviewReference(clientEngagement, suppliedReview) {
  if (clientEngagement == null || clientEngagement === "") {
    const reference = `archive_review_${crypto.randomBytes(16).toString("hex")}`;
    const reviewSha256 = crypto
      .createHash("sha256")
      .update(canonicalJson(suppliedReview), "utf8")
      .digest("hex");
    pruneReviewReferences();
    REVIEW_REFERENCES.set(reference, {
      clientEngagement: null,
      outputDir: null,
      reviewPath: null,
      review: suppliedReview,
      reviewSha256,
      runId: suppliedReview.run_id,
      expiresAt: Date.now() + REVIEW_REFERENCE_TTL_MS,
    });
    return { reference, reviewSha256, persistenceEnabled: false };
  }
  const requestedContext = requireString(clientEngagement, "client_engagement");
  if (!path.isAbsolute(requestedContext)) {
    throw new Error("client_engagement must be absolute");
  }
  const contextPath = path.resolve(requestedContext);
  const contextStat = fs.lstatSync(contextPath);
  if (!contextStat.isFile() || contextStat.isSymbolicLink()) {
    throw new Error("client_engagement must identify a regular context file");
  }
  const preflight = load_client_workflow_context_for_output(
    contextPath,
    suppliedReview.run_id,
    true,
  );
  const outputDir = fs.realpathSync(requireString(preflight.output_dir, "output_dir"));
  const outputStat = fs.statSync(outputDir);
  if (!outputStat.isDirectory()) throw new Error("review output is unavailable");
  const reviewPath = path.join(outputDir, "review_payload.json");
  const stored = readRegularJson(reviewPath, "stored review payload");
  validateReviewPayload(stored.payload);
  if (canonicalJson(stored.payload) !== canonicalJson(suppliedReview)) {
    throw new Error("review payload does not match the stored review package");
  }
  const reference = `archive_review_${crypto.randomBytes(16).toString("hex")}`;
  const reviewSha256 = crypto.createHash("sha256").update(stored.bytes).digest("hex");
  pruneReviewReferences();
  REVIEW_REFERENCES.set(reference, {
    clientEngagement: contextPath,
    persistenceEnabled: preflight.write_enabled === true,
    outputDir,
    reviewPath,
    reviewSha256,
    runId: stored.payload.run_id,
    expiresAt: Date.now() + REVIEW_REFERENCE_TTL_MS,
  });
  return { reference, reviewSha256, persistenceEnabled: preflight.write_enabled === true };
}

function resolveReviewReference(rawReference) {
  const reference = requireString(rawReference, "review_reference", 47);
  if (!/^archive_review_[0-9a-f]{32}$/.test(reference)) {
    throw new Error("review_reference is invalid");
  }
  pruneReviewReferences();
  const context = REVIEW_REFERENCES.get(reference);
  if (!context || context.expiresAt <= Date.now()) {
    throw new Error("review_reference is unknown or expired");
  }
  const stored = context.reviewPath
    ? readRegularJson(context.reviewPath, "stored review payload")
    : { payload: context.review, bytes: null };
  const currentSha256 = context.reviewPath
    ? crypto.createHash("sha256").update(stored.bytes).digest("hex")
    : crypto
        .createHash("sha256")
        .update(canonicalJson(stored.payload), "utf8")
        .digest("hex");
  if (
    currentSha256 !== context.reviewSha256 ||
    stored.payload.run_id !== context.runId
  ) {
    throw new Error("review_reference no longer matches the stored review package");
  }
  validateReviewPayload(stored.payload);
  return { reference, context, review: stored.payload };
}

function readOptionalSidecar(outputDir, name) {
  const filePath = path.join(outputDir, name);
  if (!fs.existsSync(filePath)) return null;
  return readRegularJson(filePath, name).payload;
}

function validateDecisions(inputArgs) {
  const review = validateReviewPayload(inputArgs.review_payload);
  if (!Array.isArray(inputArgs.decisions) || inputArgs.decisions.length > MAX_ITEMS) {
    throw new Error(`decisions exceeds ${MAX_ITEMS} items`);
  }
  const seen = new Set();
  const decisions = inputArgs.decisions.map((decision) => {
    if (!isPlainObject(decision)) throw new Error("decision must be an object");
    const itemId = requireString(decision.item_id, "decision.item_id", 120);
    const action = requireString(decision.action, "decision.action", 40);
    if (!review.itemIds.has(itemId)) {
      throw new Error(`decision item_id is not in review_payload.items: ${itemId}`);
    }
    if (seen.has(itemId)) throw new Error(`decisions contains duplicate item_id: ${itemId}`);
    seen.add(itemId);
    if (!ALLOWED_ACTIONS.has(action)) throw new Error(`unsupported action: ${action}`);
    const item = review.itemById.get(itemId);
    if (!item.allowed_actions.includes(action)) {
      throw new Error(`action is not allowed for item ${itemId}: ${action}`);
    }
    const editValue = decision.edit_value == null ? "" : String(decision.edit_value).trim();
    if (action === "edit" && !editValue) {
      throw new Error(`edit_value is required when action is edit`);
    }
    if (action !== "edit" && editValue) {
      throw new Error("edit_value is allowed only when action is edit");
    }
    return {
      item_id: itemId,
      action,
      reviewer_note: String(decision.reviewer_note || "").slice(0, 1000),
      edit_value: editValue,
      requested_documents: [],
    };
  });
  return decisions;
}

function pythonExecutable() {
  return process.env.VIRTUAL_ENV
    ? path.join(process.env.VIRTUAL_ENV, process.platform === "win32" ? "Scripts/python.exe" : "bin/python")
    : process.platform === "win32"
      ? "python"
      : "python3";
}

function callCli(args) {
  const result = spawnSync(pythonExecutable(), [CLI_PATH, ...args], {
    cwd: PLUGIN_ROOT,
    encoding: "utf8",
    maxBuffer: MAX_PAYLOAD_BYTES,
    timeout: 300000,
  });
  if (result.error) throw result.error;
  const lines = String(result.stdout || "").trim().split(/\r?\n/).filter(Boolean);
  let payload = null;
  if (lines.length) {
    try {
      payload = JSON.parse(lines.at(-1));
    } catch {
      throw new Error("archive organization returned invalid JSON");
    }
  }
  if (result.status !== 0 || payload?.error) {
    throw new Error(payload?.error?.message || String(result.stderr || "").trim() || "archive organization failed");
  }
  return payload;
}

function load_client_workflow_context_for_output(clientEngagement, expectedRunId, readOnly = false) {
  const result = callCli([
    "preflight",
    "--client-engagement",
    clientEngagement,
    ...(readOnly ? ["--read-only"] : []),
  ]);
  if (result.run_id !== expectedRunId) {
    throw new Error("client engagement run_id does not match review_payload.run_id");
  }
  return result;
}

function persistDecisions(inputArgs, compileApproval) {
  const resolved = resolveReviewReference(inputArgs.review_reference);
  if (!resolved.context.clientEngagement || !resolved.context.persistenceEnabled) {
    throw new Error(
      "review_reference is review-only; local persistence requires a bound Studio Archive run",
    );
  }
  const clientEngagement = resolved.context.clientEngagement;
  const boundArgs = { ...inputArgs, review_payload: resolved.review };
  load_client_workflow_context_for_output(
    clientEngagement,
    resolved.review.run_id,
  );
  const decisions = validateDecisions(boundArgs);
  const incoming = {
    schema_version: "1.0",
    plugin: "archive-organization",
    workflow: "archive-organization",
    run_id: resolved.review.run_id,
    decision_source: String(inputArgs.decision_source || "mcp_widget").slice(0, 80),
    reviewer: requireString(inputArgs.reviewer, "reviewer", 160),
    decisions,
  };
  const temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), "vera-archive-review-"));
  const temporaryDecisions = path.join(temporaryRoot, "decisions.json");
  try {
    fs.writeFileSync(temporaryDecisions, `${JSON.stringify(incoming, null, 2)}\n`, {
      encoding: "utf8",
      mode: 0o600,
      flag: "wx",
    });
    const saved = callCli([
      "save-decisions",
      "--client-engagement",
      clientEngagement,
      "--decisions",
      temporaryDecisions,
    ]);
    const savedSummary = {
      status: saved.status,
      run_id: resolved.review.run_id,
      decision_count: saved.decision_count,
      reviewer: saved.reviewer,
      review_reference: resolved.reference,
      source_archive_mutated: false,
    };
    if (!compileApproval) return savedSummary;
    const approved = callCli([
      "approve",
      "--client-engagement",
      clientEngagement,
      "--decisions",
      saved.ui_decisions_path,
    ]);
    return {
      ...savedSummary,
      status: approved.status,
      approved_change_count: approved.approved_change_count,
      execution_requires_separate_explicit_approval: true,
    };
  } finally {
    fs.rmSync(temporaryRoot, { recursive: true, force: true });
  }
}

function callTool(name, inputArgs) {
  const args = isPlainObject(inputArgs) ? inputArgs : {};
  if (name === TOOL_NAMES.validateReview) {
    validateReviewPayload(args.review_payload);
    const registered = registerReviewReference(
      args.client_engagement,
      args.review_payload,
    );
    return {
      valid: true,
      plugin: "archive-organization",
      run_id: args.review_payload.run_id,
      item_count: args.review_payload.items.length,
      review_reference: {
        reference: registered.reference,
        run_id: args.review_payload.run_id,
        review_payload_sha256: registered.reviewSha256,
        expires_in_seconds: Math.floor(REVIEW_REFERENCE_TTL_MS / 1000),
        persistence_enabled: registered.persistenceEnabled,
      },
      source_archive_mutated: false,
    };
  }
  if (name === TOOL_NAMES.renderReview) {
    const resolved = resolveReviewReference(args.review_reference);
    const canPersist = Boolean(resolved.context.clientEngagement) &&
      load_client_workflow_context_for_output(
        resolved.context.clientEngagement, resolved.review.run_id, true,
      ).write_enabled === true;
    return {
      plugin: "archive-organization",
      run_id: resolved.review.run_id,
      review_payload: resolved.review,
      ui_decisions: resolved.context.outputDir
        ? readOptionalSidecar(resolved.context.outputDir, "ui_decisions.json")
        : null,
      final_artifacts: resolved.context.outputDir
        ? readOptionalSidecar(resolved.context.outputDir, "final_artifacts.json")
        : null,
      review_reference: resolved.reference,
      persistence_enabled: canPersist,
      decision_policy: {
        save_tool: TOOL_NAMES.saveDecisions,
        apply_tool: TOOL_NAMES.applyDecisions,
        can_persist: canPersist,
        fallback: "copy_json",
      },
      execution_requires_separate_explicit_approval: true,
      source_archive_mutated: false,
    };
  }
  if (name === TOOL_NAMES.saveDecisions) return persistDecisions(args, false);
  if (name === TOOL_NAMES.applyDecisions) return persistDecisions(args, true);
  throw new Error("unknown archive organization tool");
}

function toolResult(payload) {
  const structured = { ok: true, ...payload };
  return {
    content: [
      {
        type: "text",
        text: "Archive Organization returned the structured review result attached to this tool response.",
      },
    ],
    structuredContent: structured,
    isError: false,
  };
}

function toolError(error) {
  const payload = {
    ok: false,
    error: {
      code: "archive_organization_review_failed",
      message: error instanceof Error ? error.message : String(error),
    },
  };
  return {
    content: [{ type: "text", text: JSON.stringify(payload) }],
    structuredContent: payload,
    isError: true,
  };
}

function rpcResult(id, result) {
  return { jsonrpc: "2.0", id, result };
}

function rpcError(id, code, message) {
  return { jsonrpc: "2.0", id, error: { code, message } };
}

function handleRpc(message) {
  const id = message.id ?? null;
  const params = isPlainObject(message.params) ? message.params : {};
  if (message.method === "initialize") {
    return rpcResult(id, {
      protocolVersion: params.protocolVersion || "2024-11-05",
      serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },
      capabilities: { tools: {}, resources: {} },
      instructions:
        "Validate and render the dry-run archive plan, then persist collaborator decisions. Applying review decisions only writes approved_plan.json. Never claim that it moved files; filesystem execution requires a separate explicit apply approval.",
    });
  }
  if (message.method === "notifications/initialized") return null;
  if (message.method === "tools/list") return rpcResult(id, { tools: toolDefinitions() });
  if (message.method === "tools/call") {
    try {
      return rpcResult(id, toolResult(callTool(params.name, params.arguments)));
    } catch (error) {
      return rpcResult(id, toolError(error));
    }
  }
  if (message.method === "resources/list") return rpcResult(id, { resources: resources() });
  if (message.method === "resources/read") {
    try {
      return rpcResult(id, {
        contents: [{ uri: params.uri, mimeType: WIDGET_MIME_TYPE, text: resourceText(params.uri), _meta: resources()[0]._meta }],
      });
    } catch (error) {
      return rpcError(id, -32602, error.message);
    }
  }
  if (message.method === "resources/templates/list") return rpcResult(id, { resourceTemplates: [] });
  if (message.method === "prompts/list") return rpcResult(id, { prompts: [] });
  return rpcError(id, -32601, "method not found");
}

function send(payload) {
  process.stdout.write(`${JSON.stringify(payload)}\n`);
}

function main() {
  const lines = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
  lines.on("line", (line) => {
    if (!line.trim()) return;
    try {
      const message = JSON.parse(line);
      const response = handleRpc(message);
      if (response != null) send(response);
    } catch (error) {
      send(rpcError(null, -32700, error instanceof Error ? error.message : "parse error"));
    }
  });
}

if (require.main === module) main();

module.exports = {
  TOOL_NAMES,
  callTool,
  resources,
  toolDefinitions,
  validateDecisions,
  validateReviewPayload,
};

SHA-256: 14002fa07e70a0d2494edcb70a5c7418d6c8be44a10236ee37d564ef70ef03be