import { resolveRevelaRuntime } from "./runtime-resolver" import { appendFileSync } from "fs" type JsonRpcRequest = { jsonrpc?: string id?: string | number | null method?: string params?: any } type RuntimeModule = { doctor(input?: any): any readDeckPlan(input?: any): any upsertDeckPlanSlide(input: any): any createDeckFoundation(input: any): any listPageTemplates(): any renderTemplateSlide(input: any): any addTemplateSlide(input: any): any renderTemplateScaffold(input: any): any addTemplateScaffold(input: any): any pageTemplateFoundation(input: any): any pageTemplateVocabulary(input: any): any runDeckQa(input: any): Promise exportPdf(input: any): Promise exportPptx(input: any): Promise exportPng(input: any): Promise designList(): any designRead(input?: any): any designInventory(input?: any): any designReadLayout(input: any): any designReadComponent(input: any): any designActivate(input: any): any switchDeckDesign(input: any): any designCreate(input: any): any designValidate(input: any): any designPreview(input: any): any designDraftCreate(input: any): any designDraftValidate(input: any): any designDraftInstall(input: any): any designPack(input: any): any designInstallArchive(input: any): any domainList(): any domainRead(input?: any): any domainActivate(input: any): any domainCreate(input: any): any domainValidate(input: any): any domainDraftCreate(input: any): any domainDraftValidate(input: any): any domainDraftInstall(input: any): any reviewDeckRead(input: any): Promise openDeck(input: any): Promise researchSave(input: any): any prepareLocalMaterials(input: any): Promise extractMaterial(input: any): Promise recordMaterialReview(input: any): any checkMaterialIntake(input: any): any } type MessageMode = "framed" | "raw" const tools = [ { name: "revela_doctor", description: "Inspect Revela workspace availability and basic file-native state.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root.") }), }, { name: "revela_read_deck_plan", description: "Read canonical deck-plan.md projection and diagnostics, with legacy deck-plan/ fallback support.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root.") }), }, { name: "revela_upsert_deck_plan_slide", description: "Template-aware repair helper for creating or updating one slide block in canonical deck-plan.md. Normal planning should write deck-plan.md directly and use revela_read_deck_plan for diagnostics.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), designName: stringProp("Optional design name. Defaults to the active design."), outputPath: stringProp("Optional workspace-relative HTML output path."), slideIndex: requiredNumberProp("Positive 1-based slide index."), id: stringProp("Optional stable slide node id."), title: requiredStringProp("Slide title."), chapter: requiredStringProp("Chapter name."), narrativeRole: requiredStringProp("Slide's communication job."), structural: booleanProp("Whether this is a structural slide such as cover, TOC, divider, or closing."), template: stringProp("Built-in page template id from revela_list_page_templates. New slides should use template instead of legacy layout/components."), templateContent: objectProp("Optional template content seed to write into the slide's Template Content JSON block."), layout: stringProp("Legacy design layout name. Compatibility-only for existing no-template slides."), components: componentPlanArrayProp(), visualIntent: visualIntentProp(), sourceLinks: sourceLinksProp(), narrativeLinks: narrativeLinksProp(), caveats: arrayProp("Optional caveats to keep visible in the slide plan."), }, ["slideIndex", "title", "chapter", "narrativeRole", "visualIntent"]), }, { name: "revela_create_deck_foundation", description: "Create or repair a file-native Revela HTML deck foundation shell.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), outputPath: requiredStringProp("Workspace-relative HTML output path."), title: requiredStringProp("HTML title."), language: requiredStringProp("HTML language tag."), designName: stringProp("Optional design name."), mode: enumProp(["create", "repair"], "Create or repair mode."), overwrite: booleanProp("Whether create mode may overwrite an existing file."), }, ["outputPath", "title", "language"]), }, { name: "revela_list_page_templates", description: "List built-in Revela page templates with semantic fields, content rules, and template QA contracts.", inputSchema: objectSchema({}), }, { name: "revela_render_template_slide", description: "Render one built-in page template into a stable Revela slide HTML skeleton using the active/requested design.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), designName: stringProp("Optional design name. Defaults to the active design."), templateId: requiredStringProp("Built-in template id, such as milestone or timeline."), slideIndex: requiredNumberProp("Positive 1-based slide index."), content: objectProp("Template content fields. The built-in template renderer owns the HTML skeleton."), }, ["templateId", "slideIndex", "content"]), }, { name: "revela_page_template_foundation", description: "Read the built-in template foundation for custom design authoring: scaffold HTML, CSS hooks, slots, and contract notes.", inputSchema: objectSchema({ templateId: requiredStringProp("Built-in template id, such as milestone or timeline."), }, ["templateId"]), }, { name: "revela_page_template_vocabulary", description: "Read machine-readable classes, slots, editable regions, replaceable regions, and contract notes for one page template.", inputSchema: objectSchema({ templateId: requiredStringProp("Built-in template id, such as milestone or timeline."), }, ["templateId"]), }, { name: "revela_render_template_scaffold", description: "Render one built-in page template scaffold with stable editable slots using minimal seed content.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), designName: stringProp("Optional design name. Defaults to the active design."), templateId: requiredStringProp("Built-in template id, such as milestone or timeline."), slideIndex: requiredNumberProp("Positive 1-based slide index."), seed: objectProp("Optional scaffold seed fields. This is not the final authoring interface."), }, ["templateId", "slideIndex"]), }, { name: "revela_add_template_scaffold", description: "Render and append one built-in page template scaffold into an existing Revela deck HTML file between slide markers.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), outputPath: requiredStringProp("Workspace-relative HTML deck path."), designName: stringProp("Optional design name. Defaults to the active design."), templateId: requiredStringProp("Built-in template id, such as milestone or timeline."), slideIndex: requiredNumberProp("Positive 1-based slide index."), seed: objectProp("Optional scaffold seed fields. LLM should bounded-edit the inserted slide after scaffold creation."), }, ["outputPath", "templateId", "slideIndex"]), }, { name: "revela_add_template_slide", description: "Compatibility path: render and append one built-in page template slide from full content JSON into an existing deck HTML file.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), outputPath: requiredStringProp("Workspace-relative HTML deck path."), designName: stringProp("Optional design name. Defaults to the active design."), templateId: requiredStringProp("Built-in template id, such as milestone or timeline."), slideIndex: requiredNumberProp("Positive 1-based slide index."), content: objectProp("Template content fields. Prefer scaffold-first flow for new deck creation."), }, ["outputPath", "templateId", "slideIndex", "content"]), }, { name: "revela_run_deck_qa", description: "Run Revela artifact QA on a generated HTML deck.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), file: requiredStringProp("Workspace-relative or absolute HTML deck path."), }, ["file"]), }, { name: "revela_export_pdf", description: "Export HTML to PDF. Deck HTML uses multi-page deck export; non-deck HTML falls back to a single-page artifact PDF.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), file: requiredStringProp("Workspace-relative or absolute HTML deck path."), }, ["file"]), }, { name: "revela_export_pptx", description: "Export a Revela HTML deck to PPTX.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), file: requiredStringProp("Workspace-relative or absolute HTML deck path."), }, ["file"]), }, { name: "revela_export_png", description: "Export a Revela HTML deck to one PNG file per slide.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), file: requiredStringProp("Workspace-relative or absolute HTML deck path."), outputDir: stringProp("Optional workspace-relative output directory."), }, ["file"]), }, { name: "revela_design_list", description: "List installed Revela designs and the active design.", inputSchema: objectSchema({}), }, { name: "revela_design_read", description: "Read Revela design instructions for the active or requested design.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root. Used to record deck-write hook context when section is rules."), name: stringProp("Optional design name."), section: stringProp("Optional design section, such as rules, foundation, or chart-rules."), }), }, { name: "revela_design_inventory", description: "List the active or requested Revela design sections, page templates, template slots, required classes, and assets.", inputSchema: objectSchema({ name: stringProp("Optional design name."), }), }, { name: "revela_design_activate", description: "Activate a Revela design for future deck planning and artifact generation.", inputSchema: objectSchema({ name: requiredStringProp("Design name to activate.") }, ["name"]), }, { name: "revela_switch_deck_design", description: "Switch one existing decks/*.html artifact to a design by refreshing its deck-local active design snapshot without rewriting slide content.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), file: requiredStringProp("Workspace-relative or absolute decks/*.html file."), name: requiredStringProp("Design name to use for this deck."), openBrowser: booleanProp("Whether to open the switched deck in Codex Browser. Defaults to true."), }, ["file", "name"]), }, { name: "revela_design_create", description: "Create and validate a local Revela design package from complete DESIGN.md, design.css, and optional legacy preview.html content.", inputSchema: objectSchema({ name: requiredStringProp("Design name in kebab-case."), base: stringProp("Optional base design used as structural scaffold."), designMd: requiredStringProp("Complete DESIGN.md content."), designCss: stringProp("Complete design.css content. Required for CSS-native designs; omitted only for legacy compatibility."), previewHtml: stringProp("Optional legacy preview.html content. Current design previews are generated with revela_design_preview."), assets: designAssetsProp(), overwrite: booleanProp("Whether to replace an existing local design package. Defaults to false."), }, ["name", "designMd"]), }, { name: "revela_design_validate", description: "Validate a local Revela design package.", inputSchema: objectSchema({ name: requiredStringProp("Design name to validate.") }, ["name"]), }, { name: "revela_design_draft_create", description: "Create and validate a workspace-local Revela design draft package.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), name: requiredStringProp("Design name in kebab-case."), base: stringProp("Optional base design used as structural scaffold."), designMd: requiredStringProp("Complete DESIGN.md content."), designCss: stringProp("Complete design.css content. Required for CSS-native designs; omitted only for legacy compatibility."), previewHtml: stringProp("Optional legacy preview.html content. Current design previews are generated with revela_design_preview."), assets: designAssetsProp(), overwrite: booleanProp("Whether to replace an existing workspace draft. Defaults to false."), }, ["name", "designMd"]), }, { name: "revela_design_draft_validate", description: "Validate a workspace-local Revela design draft package.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), name: requiredStringProp("Design draft name to validate."), }, ["name"]), }, { name: "revela_design_preview", description: "Generate a workspace-local preview for a draft, installed, or built-in Revela design using the built-in page template preview fixture plus the design.css file.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), name: requiredStringProp("Design name to preview."), source: enumProp(["draft", "installed", "builtin"], "Optional source. Defaults to draft when present, otherwise installed/builtin."), }, ["name"]), }, { name: "revela_design_draft_install", description: "Install a validated workspace-local Revela design draft into the user-level design registry.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), name: requiredStringProp("Design draft name to install."), overwrite: booleanProp("Whether to replace an existing user-level design package. Defaults to false."), }, ["name"]), }, { name: "revela_design_pack", description: "Package an installed or workspace-draft Revela design as a shareable .tar or .tar.gz archive.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root. Used for draft source lookup and default output path."), name: requiredStringProp("Design name in kebab-case."), source: enumProp(["draft", "installed"], "Package a workspace draft or installed design. Defaults to draft when a matching draft exists."), outputPath: stringProp("Optional archive output path. Defaults to .revela/design-archives/{name}.tar.gz under the workspace."), format: enumProp(["tar.gz", "tar"], "Archive format. Defaults to tar.gz."), overwrite: booleanProp("Whether to replace an existing archive. Defaults to false."), }, ["name"]), }, { name: "revela_design_install_archive", description: "Install a local .tar or .tar.gz Revela design archive into the user-level design registry.", inputSchema: objectSchema({ archivePath: requiredStringProp("Local path to a .tar, .tar.gz, or .tgz design archive."), name: stringProp("Optional installed design name override."), overwrite: booleanProp("Whether to replace an existing user-level design package. Defaults to false."), activate: booleanProp("Whether to activate the design after installation. Defaults to false."), }, ["archivePath"]), }, { name: "revela_domain_list", description: "List installed Revela narrative domains and the active domain.", inputSchema: objectSchema({}), }, { name: "revela_domain_read", description: "Read Revela narrative domain guidance for the active or requested domain.", inputSchema: objectSchema({ name: stringProp("Optional domain name.") }), }, { name: "revela_domain_activate", description: "Activate a Revela narrative domain for future narrative authoring guidance.", inputSchema: objectSchema({ name: requiredStringProp("Domain name to activate.") }, ["name"]), }, { name: "revela_domain_create", description: "Create and validate a local Revela narrative domain package from complete INDUSTRY.md content.", inputSchema: objectSchema({ name: requiredStringProp("Domain name in kebab-case."), domainMd: requiredStringProp("Complete INDUSTRY.md content."), overwrite: booleanProp("Whether to replace an existing local domain package. Defaults to false."), }, ["name", "domainMd"]), }, { name: "revela_domain_validate", description: "Validate a local Revela narrative domain package.", inputSchema: objectSchema({ name: requiredStringProp("Domain name to validate.") }, ["name"]), }, { name: "revela_domain_draft_create", description: "Create and validate a workspace-local Revela narrative domain draft package.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), name: requiredStringProp("Domain name in kebab-case."), domainMd: requiredStringProp("Complete INDUSTRY.md content."), overwrite: booleanProp("Whether to replace an existing workspace draft. Defaults to false."), }, ["name", "domainMd"]), }, { name: "revela_domain_draft_validate", description: "Validate a workspace-local Revela narrative domain draft package.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), name: requiredStringProp("Domain draft name to validate."), }, ["name"]), }, { name: "revela_domain_draft_install", description: "Install a validated workspace-local Revela narrative domain draft into the user-level domain registry.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), name: requiredStringProp("Domain draft name to install."), overwrite: booleanProp("Whether to replace an existing user-level domain package. Defaults to false."), }, ["name"]), }, { name: "revela_review_deck_read", description: "Read optional Review diagnostics for a Revela HTML deck: artifact QA, deck-plan diagnostics, and export/readiness signals.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), file: requiredStringProp("Workspace-relative or absolute HTML deck path."), format: enumProp(["json", "markdown"], "Return JSON only, or include a Markdown review summary."), }, ["file"]), }, { name: "revela_open_deck", description: "Open a Revela HTML deck directly in Codex Browser through a read-only localhost URL for user review.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), file: requiredStringProp("Workspace-relative or absolute HTML deck path."), openBrowser: booleanProp("Whether the tool should open the browser itself. Defaults to true when omitted."), }, ["file"]), }, { name: "revela_research_save", description: "Save source-linked research findings under researches/{topic}/{filename}.md for deck-plan use. Prefer structured blocks: ## Finding: for evidence, ## Synthesis: for interpretation across findings, ## Analysis: for analytical frameworks, ## Implementation Note: for render/data/API contracts, ## Asset Lead: for media leads, and ## Gaps for missing support.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), topic: requiredStringProp("Research topic key."), filename: requiredStringProp("Findings filename without extension."), content: requiredStringProp("Structured Markdown findings content. Evidence findings should preserve source, quote/snippet, support scope or Supports, Evidence boundary or unsupported scope, Strength, Deck use, and optional Display note. Synthesis entries should preserve Basis, Interpretation, So what, Decision implication, Confidence, Alternative reading, Evidence boundary, and Deck use. Analysis and Implementation Note entries may support planning or rendering but are not external factual proof."), sources: arrayProp("Source URLs or workspace files for YAML frontmatter."), }, ["topic", "filename", "content"]), }, { name: "revela_prepare_local_materials", description: "Scan local workspace source materials, create/update the material-intake registry, and optionally extract Office/PDF files into read views.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), path: stringProp("Optional workspace-relative subdirectory to scan."), maxDepth: numberProp("Maximum scan depth. Defaults to 2."), autoExtract: booleanProp("Whether to extract Office/PDF sources during prepare. Defaults to true."), }), }, { name: "revela_extract_document_materials", description: "Extract text, manifest, read view, and embedded images from a workspace document. Supports pdf, pptx, docx, and xlsx.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), file: requiredStringProp("Workspace-relative source file path."), }, ["file"]), }, { name: "revela_record_material_review", description: "Record that an LLM has read extracted local material and decided what was merged, deferred, ignored, or left as a gap.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), sourcePath: requiredStringProp("Workspace-relative source file path."), reviewedPaths: arrayProp("Workspace-relative extracted paths actually reviewed."), reviewSummary: requiredStringProp("Concise summary of the reviewed material."), narrativeDecisions: arrayObjectProp("Narrative decisions with kind, optional target, and rationale."), }, ["sourcePath", "reviewedPaths", "reviewSummary", "narrativeDecisions"]), }, { name: "revela_check_material_intake", description: "Check whether scanned Office/PDF sources were extracted and reviewed before being treated as narrative intake.", inputSchema: objectSchema({ workspaceRoot: stringProp("Optional workspace root."), strictness: enumProp(["authoring", "readiness", "render"], "Check strictness."), }), }, ] let runtimePromise: Promise | undefined const debugEnabled = process.env.REVELA_MCP_DEBUG === "1" const bootLogEnabled = process.env.REVELA_MCP_BOOT_LOG === "1" const bootLogPath = "/tmp/revela-mcp-boot.log" let activeResponseMode: MessageMode = "framed" async function runtime(): Promise { runtimePromise ??= import(runtimeUrl()) as Promise return runtimePromise } function runtimeUrl(): string { const pluginRoot = new URL("..", import.meta.url).pathname const resolved = resolveRevelaRuntime({ pluginRoot }) if (!resolved.ok || !resolved.runtimePath) { throw new Error(`Could not resolve Revela runtime. ${resolved.diagnostics.join(" ")}`) } debug("runtime", { pluginRoot, source: resolved.source, runtimePath: resolved.runtimePath }) return new URL(`file://${resolved.runtimePath}`).href } async function handle(req: JsonRpcRequest): Promise { if (!req.id && String(req.method || "").startsWith("notifications/")) return undefined try { debug("request", { id: req.id, method: req.method }) bootLog("request", { id: req.id, method: req.method }) if (req.method === "initialize") { bootLog("initialize-received", { id: req.id, protocolVersion: req.params?.protocolVersion }) const runtimeInfo = await runtime() const doctor = runtimeInfo.doctor({}) return result(req.id, { protocolVersion: req.params?.protocolVersion || "2024-11-05", capabilities: { tools: {} }, serverInfo: { name: "revela", version: doctor.version }, }) } if (req.method === "tools/list") { return result(req.id, { tools }) } if (req.method === "tools/call") { const name = req.params?.name const args = req.params?.arguments ?? {} const value = await callTool(name, args) return result(req.id, { content: [{ type: "text", text: JSON.stringify(value, null, 2) }], }) } return error(req.id, -32601, `Unknown method: ${req.method}`) } catch (e) { return error(req.id, -32000, e instanceof Error ? e.message : String(e)) } } async function callTool(name: string, args: any): Promise { const r = await runtime() if (name === "revela_doctor") return r.doctor(args) if (name === "revela_read_deck_plan") return r.readDeckPlan(args) if (name === "revela_upsert_deck_plan_slide") return r.upsertDeckPlanSlide(args) if (name === "revela_create_deck_foundation") return r.createDeckFoundation(args) if (name === "revela_list_page_templates") return r.listPageTemplates() if (name === "revela_render_template_slide") return r.renderTemplateSlide(args) if (name === "revela_add_template_slide") return r.addTemplateSlide(args) if (name === "revela_page_template_foundation") return r.pageTemplateFoundation(args) if (name === "revela_page_template_vocabulary") return r.pageTemplateVocabulary(args) if (name === "revela_render_template_scaffold") return r.renderTemplateScaffold(args) if (name === "revela_add_template_scaffold") return r.addTemplateScaffold(args) if (name === "revela_run_deck_qa") return r.runDeckQa(args) if (name === "revela_export_pdf") return r.exportPdf(args) if (name === "revela_export_pptx") return r.exportPptx(args) if (name === "revela_export_png") return r.exportPng(args) if (name === "revela_design_list") return r.designList() if (name === "revela_design_read") return r.designRead(args) if (name === "revela_design_inventory") return r.designInventory(args) if (name === "revela_design_activate") return r.designActivate(args) if (name === "revela_switch_deck_design") return r.switchDeckDesign(args) if (name === "revela_design_create") return r.designCreate(args) if (name === "revela_design_validate") return r.designValidate(args) if (name === "revela_design_draft_create") return r.designDraftCreate(args) if (name === "revela_design_draft_validate") return r.designDraftValidate(args) if (name === "revela_design_preview") return r.designPreview(args) if (name === "revela_design_draft_install") return r.designDraftInstall(args) if (name === "revela_design_pack") return r.designPack(args) if (name === "revela_design_install_archive") return r.designInstallArchive(args) if (name === "revela_domain_list") return r.domainList() if (name === "revela_domain_read") return r.domainRead(args) if (name === "revela_domain_activate") return r.domainActivate(args) if (name === "revela_domain_create") return r.domainCreate(args) if (name === "revela_domain_validate") return r.domainValidate(args) if (name === "revela_domain_draft_create") return r.domainDraftCreate(args) if (name === "revela_domain_draft_validate") return r.domainDraftValidate(args) if (name === "revela_domain_draft_install") return r.domainDraftInstall(args) if (name === "revela_review_deck_read") return r.reviewDeckRead(args) if (name === "revela_open_deck") return r.openDeck(args) if (name === "revela_research_save") return r.researchSave(args) if (name === "revela_prepare_local_materials") return r.prepareLocalMaterials(args) if (name === "revela_extract_document_materials") return r.extractMaterial(args) if (name === "revela_record_material_review") return r.recordMaterialReview(args) if (name === "revela_check_material_intake") return r.checkMaterialIntake(args) throw new Error(`Unknown tool: ${name}`) } function result(id: JsonRpcRequest["id"], value: any): any { return { jsonrpc: "2.0", id, result: value } } function error(id: JsonRpcRequest["id"], code: number, message: string): any { return { jsonrpc: "2.0", id, error: { code, message } } } function objectSchema(properties: Record, required: string[] = []) { return { type: "object", properties, required, additionalProperties: false } } function requiredStringProp(description: string) { return { type: "string", description } } function stringProp(description: string) { return { type: "string", description } } function booleanProp(description: string) { return { type: "boolean", description } } function numberProp(description: string) { return { type: "number", description } } function requiredNumberProp(description: string) { return { type: "number", description } } function enumProp(values: string[], description: string) { return { type: "string", enum: values, description } } function arrayProp(description: string) { return { type: "array", items: { type: "string" }, description } } function objectProp(description: string) { return { type: "object", description, additionalProperties: true } } function stringOrArrayProp(description: string) { return { anyOf: [ { type: "string" }, { type: "array", items: { type: "string" } }, ], description, } } function arrayObjectProp(description: string) { return { type: "array", description, items: { type: "object", properties: { kind: { type: "string", enum: ["merged", "gap", "ignored", "deferred"] }, target: { type: "string" }, rationale: { type: "string" }, }, required: ["kind", "rationale"], additionalProperties: false, }, } } function designAssetsProp() { return { type: "array", description: "Optional design-owned assets for user-uploaded or local materials to archive under assets/**. Each item must use path plus content, contentBase64, or sourcePath.", items: { type: "object", properties: { path: { type: "string", description: "Package-relative asset path for uploaded or local design material. Must start with assets/." }, content: { type: "string", description: "UTF-8 text asset content." }, contentBase64: { type: "string", description: "Base64-encoded binary asset content." }, sourcePath: { type: "string", description: "Local file path to copy into the design asset." }, }, required: ["path"], additionalProperties: false, }, } } function componentPlanArrayProp() { const childSchema: any = { type: "object", properties: { name: { type: "string", description: "Design component name." }, slot: { type: "string", description: "Layout semantic slot from design inventory, such as left, right, top, bottom, fullbleed, or a numbered slot." }, position: { type: "string", description: "Slot-local kebab-case anchor, such as left-top, center, or overlay-top-right." }, purpose: { type: "string", description: "Component communication job." }, content: { type: "string", description: "Exact text, data, or structure to render." }, claimIds: { type: "array", items: { type: "string" } }, evidenceIds: { type: "array", items: { type: "string" } }, sourceNotes: { type: "array", items: { type: "string" } }, renderNotes: { type: "array", items: { type: "string" } }, placementNote: { type: "string" }, }, required: ["name", "slot", "position", "purpose", "content"], additionalProperties: false, } const componentSchema: any = JSON.parse(JSON.stringify(childSchema)) componentSchema.properties.children = { type: "array", description: "Nested child components. Use box as the semantic container for grouped child content.", items: childSchema, } return { type: "array", description: "Structured component plan entries.", items: componentSchema, } } function visualIntentProp() { return { anyOf: [ { type: "string" }, { type: "object", properties: { kind: { type: "string" }, component: { type: "string" }, rationale: { type: "string" }, brief: { type: "string" }, }, additionalProperties: false, }, ], description: "Visual plan or structured visual intent. If component is set, it must exist in components[].name.", } } function narrativeLinksProp() { return { type: "object", properties: { claimIds: { type: "array", items: { type: "string" } }, evidenceIds: { type: "array", items: { type: "string" } }, riskIds: { type: "array", items: { type: "string" } }, objectionIds: { type: "array", items: { type: "string" } }, gapIds: { type: "array", items: { type: "string" } }, }, additionalProperties: false, } } function sourceLinksProp() { return { type: "object", description: "Deck-first source links. Use these instead of narrativeLinks for new plans.", properties: { materials: { type: "array", items: { type: "string" } }, findings: { type: "array", items: { type: "string" } }, assets: { type: "array", items: { type: "string" } }, urls: { type: "array", items: { type: "string" } }, caveats: { type: "array", items: { type: "string" } }, }, additionalProperties: false, } } function writeMessage(message: any, mode: MessageMode = activeResponseMode): void { activeResponseMode = mode const body = JSON.stringify(message) debug("response", { id: message?.id, mode, result: message?.result ? Object.keys(message.result) : undefined, error: message?.error?.message, }) bootLog("response-written", { id: message?.id, mode, result: message?.result ? Object.keys(message.result) : undefined, error: message?.error?.message, bytes: Buffer.byteLength(body, "utf8"), }) if (mode === "raw") { process.stdout.write(`${body}\n`) return } process.stdout.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`) } async function main(): Promise { debug("startup", { script: import.meta.path }) bootLog("server-loaded", { script: import.meta.path, cwd: process.cwd() }) const reader = Bun.stdin.stream().getReader() bootLog("stdin-reader-started", {}) let buffer: Uint8Array = new Uint8Array() while (true) { const { value, done } = await reader.read() if (done) { bootLog("stdin-done", { bufferedBytes: buffer.byteLength }) break } buffer = concatBytes(buffer, value) const parsed = parseMessages(buffer) const responseMode = parsed.mode || activeResponseMode activeResponseMode = responseMode if (parsed.messages.length > 0) { debug("parse", { mode: parsed.mode, messages: parsed.messages.length }) bootLog("messages-parsed", { mode: parsed.mode, messages: parsed.messages.length, remainingBytes: parsed.remaining.byteLength }) } buffer = parsed.remaining for (const message of parsed.messages) { const response = await handle(message) if (response) writeMessage(response, responseMode) } } const trimmed = decode(buffer).trim() if (trimmed) { bootLog("line-buffer-parse", { chars: trimmed.length }) for (const message of parseLineMessages(trimmed)) { const response = await handle(message) if (response) writeMessage(response, "raw") } } } function parseFramedMessages(input: Uint8Array): { messages: JsonRpcRequest[] remaining: Uint8Array } { const messages: JsonRpcRequest[] = [] let cursor = 0 while (cursor < input.byteLength) { const headerEnd = indexOfHeaderEnd(input, cursor) if (headerEnd === -1) break const header = decode(input.slice(cursor, headerEnd)) const match = /Content-Length:\s*(\d+)/i.exec(header) if (!match) break const length = Number(match[1]) const start = headerEnd + 4 const end = start + length if (input.byteLength < end) break messages.push(JSON.parse(decode(input.slice(start, end)))) cursor = end } return { messages, remaining: input.slice(cursor) } } function parseMessages(input: Uint8Array): { messages: JsonRpcRequest[] remaining: Uint8Array mode?: "framed" | "raw" } { const framed = parseFramedMessages(input) if (framed.messages.length > 0) return { ...framed, mode: "framed" } const remainingText = decode(framed.remaining) if (/^\s*Content-Length:/i.test(remainingText) && !remainingText.includes("\r\n\r\n")) { return { messages: [], remaining: input } } const raw = parseRawJsonMessages(remainingText) if (raw.messages.length > 0) return { messages: raw.messages, remaining: encode(raw.remaining), mode: "raw" } return framed } function parseLineMessages(input: string): JsonRpcRequest[] { const messages: JsonRpcRequest[] = [] for (const line of input.split(/\n/).map((item) => item.trim()).filter(Boolean)) { messages.push(JSON.parse(line)) } return messages } function parseRawJsonMessages(input: string): { messages: JsonRpcRequest[]; remaining: string } { const messages: JsonRpcRequest[] = [] let start = -1 let depth = 0 let inString = false let escaped = false let cursor = 0 for (let i = 0; i < input.length; i++) { const char = input[i] if (start === -1) { if (/\s/.test(char)) { cursor = i + 1 continue } if (char !== "{" && char !== "[") return { messages, remaining: input.slice(cursor) } start = i depth = 1 inString = false escaped = false continue } if (inString) { if (escaped) { escaped = false } else if (char === "\\") { escaped = true } else if (char === "\"") { inString = false } continue } if (char === "\"") { inString = true } else if (char === "{" || char === "[") { depth++ } else if (char === "}" || char === "]") { depth-- if (depth === 0) { const end = i + 1 messages.push(JSON.parse(input.slice(start, end))) cursor = end start = -1 } } } return { messages, remaining: input.slice(start === -1 ? cursor : start) } } function concatBytes(a: Uint8Array, b: Uint8Array): Uint8Array { const next = new Uint8Array(a.byteLength + b.byteLength) next.set(a) next.set(b, a.byteLength) return next } function indexOfHeaderEnd(input: Uint8Array, offset: number): number { for (let i = offset; i <= input.byteLength - 4; i++) { if (input[i] === 13 && input[i + 1] === 10 && input[i + 2] === 13 && input[i + 3] === 10) return i } return -1 } function decode(input: Uint8Array): string { return new TextDecoder().decode(input) } function encode(input: string): Uint8Array { return new TextEncoder().encode(input) } function debug(event: string, data: Record): void { if (!debugEnabled) return process.stderr.write(`[revela-mcp] ${event} ${JSON.stringify(data)}\n`) } function bootLog(event: string, data: Record): void { if (!bootLogEnabled) return try { appendFileSync(bootLogPath, `${new Date().toISOString()} ${event} ${JSON.stringify(data)}\n`, "utf8") } catch { // Diagnostics must never interfere with the MCP stdio protocol. } } main().catch((e) => { bootLog("top-level-error", { error: e instanceof Error ? e.stack || e.message : String(e) }) writeMessage(error(null, -32000, e instanceof Error ? e.message : String(e))) })