UNPKG

@receiz/mcp-server

Version:

MCP server for agents building and verifying Receiz proof-native applications through the canonical Receiz SDK rails.

251 lines • 19 kB
import { ArtifactInputError, readAuthorizedUploadedArtifact } from "./uploadedArtifact.js"; import { createHash } from "node:crypto"; import { readFile, realpath, open, mkdtemp } from "node:fs/promises"; import { homedir } from "node:os"; import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path"; import { createReceizNodeOfflineSealer } from "@receiz/sdk/offline/node"; import { verifyReceizOfflineSealedFile } from "@receiz/sdk/offline"; import { createReceizNodeOfflineKaiProofFactory } from "@receiz/sdk/offline/kai/node"; import { admitReceizKksV1ProofCoordinate } from "@receiz/sdk"; import { RECEIZ_SEALED_OUTPUT_URI } from "./sealedArtifactOutput.js"; // One proof process per MCP host, retaining the live monotonic sampler across // calls. A tool invocation must not leave an additional idle process behind. let kaiProofFactory; export const RECEIZ_OFFLINE_SEAL_TOOLS = [ { name: "receiz_offline_kai_proof", description: "MCP is not authority. Create and verify a full live KaiSigil Groth16 coordinate locally. No pulse override. An optional predecessor must be a complete valid proof coordinate; the consuming primitive still checks its expected causal head.", inputSchema: { type: "object", properties: { previousCoordinate: { type: "object" } }, additionalProperties: false } }, { name: "receiz_offline_seal_status", description: "MCP is not authority. Check local canonical signer and packaged proof resources. No network access.", inputSchema: { type: "object", properties: {}, additionalProperties: false } }, { name: "receiz_offline_seal_enroll", description: "MCP is not authority. Explicit one-time online canonical device enrollment. The private key remains in host custody. No login required.", inputSchema: { type: "object", properties: { confirmEnrollment: { type: "boolean", const: true } }, required: ["confirmEnrollment"], additionalProperties: false } }, { name: "receiz_offline_seal_file", description: "Use for 'seal this', 'Receiz this', 'record this moment', or their equivalents in any language, with the exact selected file, response or supplied text. Resolve the source from context; no technical prompt is needed. Seal with the executing device's existing admitted signer, then independently verify the saved output with receiz_offline_verify_file and offer the branded Download sealed proof action; never invent sandbox links or offer a raw carrier as the original download. A moment uses the existing canonical Kai proof; do not invent a capture or observation. Workspace files require inputPath/outputPath; original uploaded/generated files use file; supplied text uses text/contentKind. File/text inputs create a new private output. After enrollment, sealing needs no network; attachment retrieval needs its authorized URL. Text does not prove authorship or complete chat history. MCP is not authority.", inputSchema: { type: "object", properties: { inputPath: { type: "string" }, outputPath: { type: "string" }, mimeType: { type: "string" }, file: { type: ["object", "string"], description: "Original platform attachment reference or host-staged attachment path; never an arbitrary device custody path.", properties: { download_url: { type: "string" }, file_id: { type: "string" }, mime_type: { type: "string" }, file_name: { type: "string" } }, required: ["download_url", "file_id"], additionalProperties: false }, text: { type: "string" }, filename: { type: "string" }, contentKind: { type: "string", enum: ["response", "conversation", "text"] }, }, additionalProperties: false }, outputSchema: { type: "object", properties: { artifactReference: { type: "string" }, artifactSha256: { type: "string" }, suggestedFilename: { type: "string" }, mimeType: { type: "string" }, byteLength: { type: "integer" } }, required: ["artifactReference", "artifactSha256", "suggestedFilename", "mimeType", "byteLength"] }, _meta: { "openai/fileParams": ["file"], ui: { resourceUri: RECEIZ_SEALED_OUTPUT_URI }, "openai/outputTemplate": RECEIZ_SEALED_OUTPUT_URI } }, { name: "receiz_offline_artifact_chunk", description: "MCP is not authority. App-only exact sealed output transport. Reopens a recently sealed device-held proof object, checks its exact digest, then returns a bounded byte segment to the component. When the host cannot download the file, the component may request a short-lived first-party Receiz download ticket for those exact chunks; the ticket conveys no proof authority.", inputSchema: { type: "object", properties: { transferToken: { type: "string" }, artifactReference: { type: "string" }, artifactSha256: { type: "string" }, offset: { type: "integer", minimum: 0 }, prepareDownload: { type: "boolean" }, attemptIds: { type: "array", items: { type: "string" } }, prepareCarrier: { type: "boolean", description: "Hosted app-only projection of completed exact source chunks into the existing verified Receiz bundle carrier. No sealing or history append." }, byteLength: { type: "integer", minimum: 1 } }, additionalProperties: false }, outputSchema: { type: "object", properties: { offset: { type: "integer" }, nextOffset: { type: "integer" }, byteLength: { type: "integer" }, artifactSha256: { type: "string" }, filename: { type: "string" }, mimeType: { type: "string" }, mcpAuthority: { type: "boolean" }, bytesBase64: { type: "string" }, receizTransportAttemptId: { type: "string" }, downloadUrl: { type: "string" } }, required: ["offset", "nextOffset", "byteLength", "artifactSha256", "filename", "mimeType", "mcpAuthority"] }, _meta: { ui: { visibility: ["app"] }, "openai/visibility": "private", "openai/widgetAccessible": true } }, { name: "receiz_offline_verify_file", description: "MCP is not authority. Verify complete workspace artifact bytes offline using the canonical SDK verifier.", inputSchema: { type: "object", properties: { inputPath: { type: "string" } }, required: ["inputPath"], additionalProperties: false } }, ]; /** A reusable application boundary: only exact source bytes enter the SDK sealer. * The sealed file is authoritative; this result is a locator and projection. */ export async function sealReceizSourceBytes(input, options = {}) { if (!(input.bytes instanceof Uint8Array) || !input.bytes.length || !input.filename.trim()) throw Error("offline_seal_input_invalid"); const workspace = await realpath(options.workspace ?? process.env.RECEIZ_OFFLINE_WORKSPACE ?? process.cwd()); const directory = resolve(options.directory ?? process.env.RECEIZ_OFFLINE_SEAL_DIRECTORY ?? join(homedir(), ".receiz", "offline-seal")); async function canonicalPath(path) { try { return await realpath(path); } catch (error) { if (error.code !== "ENOENT") throw error; const parent = dirname(path); if (parent === path) throw error; return join(await canonicalPath(parent), basename(path)); } } const custodyRoot = await canonicalPath(directory); const workspaceWithinCustody = relative(custodyRoot, workspace); if (workspaceWithinCustody === "" || (!workspaceWithinCustody.startsWith("..") && !isAbsolute(workspaceWithinCustody))) { throw Error("offline_seal_path_outside_workspace"); } const identity = options.identity !== undefined ? options.identity : process.env.RECEIZ_SUBJECT_IDENTITY_PATH ? { artifactPath: process.env.RECEIZ_SUBJECT_IDENTITY_PATH, passphrase: process.env.RECEIZ_SUBJECT_IDENTITY_PASSPHRASE ?? "" } : null; const sealer = options.sealer ?? await createReceizNodeOfflineSealer({ directory, ...(identity ? { identityArtifactPath: identity.artifactPath, identityPassphrase: identity.passphrase ?? "" } : {}), ...(options.fetch ? { fetch: options.fetch } : {}) }); const safeName = basename(input.filename).replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 160); if (!safeName || safeName === "." || safeName === "..") throw Error("offline_seal_input_invalid"); const sealed = await sealer.seal({ bytes: input.bytes, filename: safeName, ...(input.mimeType ? { mimeType: input.mimeType } : {}) }); if (!sealed.verification.ok) throw Error("offline_seal_output_verification_failed"); const outputDirectory = await mkdtemp(join(workspace, ".receiz-sealed-")); const outputName = basename(sealed.filename); if (!outputName || outputName === "." || outputName === "..") throw Error("offline_seal_output_invalid"); const outputPath = join(outputDirectory, outputName); const output = await open(outputPath, "wx", 0o600); try { await output.writeFile(sealed.artifactBytes); await output.sync(); } finally { await output.close(); } const artifactSha256 = createHash("sha256").update(sealed.artifactBytes).digest("hex"); // Device material host reopens this exact relative file and re-verifies the // enclosing artifact. The reference is a locator, never proof authority. const artifactReference = `receiz:file:${artifactSha256}:${relative(workspace, outputPath).split(sep).join("/")}`; return { outputPath, artifactReference, artifactSha256, suggestedFilename: outputName, mimeType: sealed.mimeType, byteLength: sealed.artifactBytes.length, verification: sealed.verification, mcpAuthority: false, strongerTruth: "sealed-receiz-proof-object" }; } export async function sealUploadedArtifact(raw, options = {}) { const source = await readAuthorizedUploadedArtifact(raw, options); // OpenAI may omit file_name. Preserve a native image carrier when its // declared MIME gives an unambiguous extension; MIME is still only a hint. const extension = source.mimeType === "image/png" ? ".png" : source.mimeType === "image/jpeg" ? ".jpg" : source.mimeType === "image/gif" ? ".gif" : source.mimeType === "image/webp" ? ".webp" : source.mimeType === "application/pdf" ? ".pdf" : ""; const filename = source.filename === "attachment" ? `attachment${extension}` : source.filename; const sealed = await sealReceizSourceBytes({ bytes: source.bytes, filename, ...(source.mimeType ? { mimeType: source.mimeType } : {}) }, options); return { ...sealed, sourceTransport: source.transport, sourceKind: "authorized-upload" }; } export async function sealSuppliedText(raw, options = {}) { if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw Error("offline_seal_arguments_invalid"); const args = raw; if (Object.keys(args).some(key => !["text", "filename", "contentKind", "mimeType"].includes(key)) || (args.mimeType !== undefined && (typeof args.mimeType !== "string" || !args.mimeType.trim() || /[\r\n]/.test(args.mimeType))) || typeof args.text !== "string" || !args.text || Buffer.byteLength(args.text, "utf8") > 512 * 1024 || !["response", "conversation", "text"].includes(String(args.contentKind)) || (args.filename !== undefined && (typeof args.filename !== "string" || !/^[a-zA-Z0-9._-]{1,120}$/.test(args.filename)))) { throw Error("offline_seal_arguments_invalid"); } const filename = typeof args.filename === "string" ? args.filename : `${args.contentKind}.txt`; const bytes = Buffer.from(args.text, "utf8"); const sealed = await sealReceizSourceBytes({ bytes, filename, mimeType: typeof args.mimeType === "string" ? args.mimeType : "text/plain" }, options); return { ...sealed, sourceKind: "caller-supplied-text", contentKind: args.contentKind, sourceTransport: { byteLength: bytes.length, sha256: createHash("sha256").update(bytes).digest("hex"), authority: false }, authorshipVerified: false, conversationCompletenessVerified: false }; } export async function callReceizOfflineSealTool(name, raw, options = {}) { if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("offline_seal_arguments_invalid"); const args = raw; const allowed = name === "receiz_offline_seal_enroll" ? ["confirmEnrollment"] : name === "receiz_offline_kai_proof" ? ["previousCoordinate"] : name === "receiz_offline_seal_file" ? ["inputPath", "outputPath", "mimeType", "file", "text", "filename", "contentKind"] : name === "receiz_offline_verify_file" ? ["inputPath"] : []; if (Object.keys(args).some(key => !allowed.includes(key))) throw new Error("offline_seal_arguments_invalid"); if (name === "receiz_offline_seal_file" && Object.hasOwn(args, "file")) { // ChatGPT may send these schema-advertised hints with its selected file. // The authorized attachment carries the actual bytes, name, and MIME; // caller hints never override that stronger source. if (Object.keys(args).some(key => !["file", "filename", "mimeType"].includes(key)) || (args.filename !== undefined && typeof args.filename !== "string") || (args.mimeType !== undefined && typeof args.mimeType !== "string")) throw Error("offline_seal_arguments_invalid"); return sealUploadedArtifact({ file: args.file }, options); } if (name === "receiz_offline_seal_file" && Object.hasOwn(args, "text")) { if (Object.keys(args).some(key => !["text", "filename", "contentKind", "mimeType"].includes(key))) throw Error("offline_seal_arguments_invalid"); return sealSuppliedText(args, options); } if (name === "receiz_offline_seal_file" && Object.keys(args).some(key => !["inputPath", "outputPath", "mimeType"].includes(key))) { throw Error("offline_seal_arguments_invalid"); } if (name === "receiz_offline_kai_proof") { const previous = args.previousCoordinate === undefined ? undefined : await admitReceizKksV1ProofCoordinate(args.previousCoordinate); const createCoordinate = await (kaiProofFactory ??= createReceizNodeOfflineKaiProofFactory()); const coordinate = await createCoordinate({ previousKaiSigilProofHead: previous?.kaiSigil.proofHead ?? null }); await admitReceizKksV1ProofCoordinate(coordinate, { expectedPreviousKaiSigilProofHead: previous?.kaiSigil.proofHead ?? null, ...(previous ? { minimumExclusiveKaiUpulse: previous.kaiUpulse } : {}), }); return { coordinate, mcpAuthority: false, networkRequired: false }; } const directory = resolve(options.directory ?? process.env.RECEIZ_OFFLINE_SEAL_DIRECTORY ?? join(homedir(), ".receiz", "offline-seal")); // Resolve existing ancestors too: custody may not exist before enrollment. async function canonicalPath(path) { try { return await realpath(path); } catch (error) { if (error.code !== "ENOENT") throw error; const parent = dirname(path); if (parent === path) throw error; return join(await canonicalPath(parent), basename(path)); } } const custodyRoot = await canonicalPath(directory); const workspace = await realpath(options.workspace ?? process.env.RECEIZ_OFFLINE_WORKSPACE ?? process.cwd()); function inside(root, path) { const rel = relative(root, path); return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel)); } async function filePath(key, output = false) { const value = args[key]; if (typeof value !== "string" || !value.trim()) throw new Error("offline_seal_path_required"); const candidate = resolve(workspace, value); const path = output ? join(await realpath(dirname(candidate)), basename(candidate)) : await realpath(candidate); if (!inside(workspace, path) || inside(custodyRoot, path)) throw new Error("offline_seal_path_outside_workspace"); return path; } if (name === "receiz_offline_verify_file") { const inputPath = await filePath("inputPath"); const bytes = await readFile(inputPath); try { const verification = await verifyReceizOfflineSealedFile({ bytes, filename: basename(inputPath) }); return { verification, mcpAuthority: false }; } catch { throw new ArtifactInputError("artifact_verification_unavailable", "canonical_verification"); } } const identity = options.identity !== undefined ? options.identity : process.env.RECEIZ_SUBJECT_IDENTITY_PATH ? { artifactPath: process.env.RECEIZ_SUBJECT_IDENTITY_PATH, passphrase: process.env.RECEIZ_SUBJECT_IDENTITY_PASSPHRASE ?? "" } : null; const sealer = options.sealer ?? await createReceizNodeOfflineSealer({ directory, ...(identity ? { identityArtifactPath: identity.artifactPath, identityPassphrase: identity.passphrase ?? "" } : {}), ...(options.fetch ? { fetch: options.fetch } : {}) }); if (name === "receiz_offline_seal_status") return { ready: await sealer.ready(), networkRequiredForSealing: false, mcpAuthority: false }; if (name === "receiz_offline_seal_enroll") { if (args.confirmEnrollment !== true) throw new Error("offline_seal_enrollment_confirmation_required"); await sealer.enroll(); return { ready: await sealer.ready(), privateKeyExposed: false, mcpAuthority: false }; } if (name !== "receiz_offline_seal_file") throw new Error("offline_seal_tool_unknown"); if (args.mimeType !== undefined && typeof args.mimeType !== "string") throw new Error("offline_seal_arguments_invalid"); const inputPath = await filePath("inputPath"); const outputPath = await filePath("outputPath", true); const sealed = await sealer.seal({ bytes: await readFile(inputPath), filename: basename(inputPath), ...(typeof args.mimeType === "string" ? { mimeType: args.mimeType } : {}) }); if (!sealed.verification.ok) throw Error("offline_seal_output_verification_failed"); const output = await open(outputPath, "wx", 0o600); try { await output.writeFile(sealed.artifactBytes); await output.sync(); } finally { await output.close(); } const artifactSha256 = createHash("sha256").update(sealed.artifactBytes).digest("hex"); const verification = sealed.verification; return { outputPath, artifactSha256, artifactReference: `receiz:file:${artifactSha256}:${relative(workspace, outputPath).split(sep).join("/")}`, suggestedFilename: basename(outputPath), mimeType: sealed.mimeType, byteLength: sealed.artifactBytes.length, verification, mcpAuthority: false, strongerTruth: "sealed-receiz-proof-object" }; } //# sourceMappingURL=offlineSealing.js.map