@receiz/mcp-server
Version:
MCP server for agents building and verifying Receiz proof-native applications through the canonical Receiz SDK rails.
190 lines • 9.31 kB
JavaScript
import { createHash } from "node:crypto";
import { open, realpath } from "node:fs/promises";
import { constants } from "node:fs";
import { basename, isAbsolute, relative } from "node:path";
import { verifyReceizOfflineSealedFile } from "@receiz/sdk/offline";
export class ArtifactInputError extends Error {
code;
stage;
constructor(code, stage) {
super(code);
this.code = code;
this.stage = stage;
}
}
export const RECEIZ_VERIFY_UPLOADED_ARTIFACT_TOOL = {
name: "receiz_verify_uploaded_artifact",
description: "Use for short requests such as 'verify this', 'Receiz verify this', or their equivalents in any language when the user selects an attachment. Supply the selected original file reference; the user need not name a tool or provide a technical prompt. Retrieve the authorized bytes and run the canonical Receiz verifier in the executing host. No signing, claiming, resealing, appending or publishing. A ChatGPT /mnt/data path is not a device workspace path. MCP is not authority. The download reference is not proof authority.",
inputSchema: { type: "object", properties: { file: { type: ["object", "string"], description: "Original platform attachment reference, or an absolute path inside the host-configured attachment staging roots. Never a preview or arbitrary device file.", properties: {
download_url: { type: "string" }, file_id: { type: "string" }, mime_type: { type: "string" }, file_name: { type: "string" },
}, required: ["download_url", "file_id"], additionalProperties: false } }, required: ["file"], additionalProperties: false },
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false, idempotentHint: true },
_meta: { "openai/fileParams": ["file"] },
};
const MAX_BYTES = 32 * 1024 * 1024;
const DOWNLOAD_TIMEOUT_MS = 15_000;
const record = (v) => !!v && typeof v === "object" && !Array.isArray(v);
async function readStagedAttachment(path) {
const deny = () => new ArtifactInputError("attachment_authorization_failed", "attachment_authorization");
if (!isAbsolute(path) || !path.trim() || path.length > 16_384)
throw deny();
let roots;
try {
roots = JSON.parse(process.env.RECEIZ_ATTACHMENT_LOCAL_ROOTS ?? "[]");
}
catch {
throw deny();
}
if (!Array.isArray(roots) || !roots.length || roots.some(root => typeof root !== "string" || !isAbsolute(root)))
throw deny();
let canonical;
try {
canonical = await realpath(path);
}
catch {
throw new ArtifactInputError("attachment_unavailable", "file_reading");
}
let admitted = false;
for (const root of roots) {
let base;
try {
base = await realpath(root);
}
catch {
continue;
}
const rel = relative(base, canonical);
if (rel && rel !== ".." && !rel.startsWith("../") && !rel.startsWith("..\\") && !isAbsolute(rel))
admitted = true;
}
if (!admitted)
throw deny();
try {
const handle = await open(canonical, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
try {
const stat = await handle.stat();
if (!stat.isFile() || stat.nlink !== 1)
throw deny();
if (stat.size > MAX_BYTES)
throw new ArtifactInputError("attachment_too_large", "file_reading");
const chunks = [];
let total = 0;
for (;;) {
const chunk = Buffer.alloc(Math.min(64 * 1024, MAX_BYTES - total + 1));
const { bytesRead } = await handle.read(chunk);
if (!bytesRead)
break;
total += bytesRead;
if (total > MAX_BYTES)
throw new ArtifactInputError("attachment_too_large", "file_reading");
chunks.push(chunk.subarray(0, bytesRead));
}
if (!total)
throw new ArtifactInputError("attachment_unavailable", "file_reading");
const bytes = Buffer.concat(chunks, total);
return { bytes, filename: basename(path), mimeType: undefined,
transport: { byteLength: total, sha256: createHash("sha256").update(bytes).digest("hex"), authority: false } };
}
finally {
await handle.close();
}
}
catch (error) {
if (error instanceof ArtifactInputError)
throw error;
throw new ArtifactInputError("attachment_unavailable", "file_reading");
}
}
/** Host configuration only; never accept permitted origins through a tool call.
* The enclosing relay authenticates the request owner and retains exact attempts.
* A selected file's capability URL grants retrieval only, never artifact authority.
*/
export async function readAuthorizedUploadedArtifact(raw, options = {}) {
const deny = () => new ArtifactInputError("attachment_authorization_failed", "attachment_authorization");
if (!record(raw) || Object.keys(raw).some(k => k !== "file"))
throw deny();
if (typeof raw.file === "string")
return readStagedAttachment(raw.file);
if (!record(raw.file))
throw deny();
const file = raw.file;
if (Object.keys(file).some(k => !["download_url", "file_id", "mime_type", "file_name"].includes(k)) ||
typeof file.download_url !== "string" || file.download_url.length > 16_384 ||
typeof file.file_id !== "string" || !file.file_id.trim() || file.file_id.length > 512 ||
["mime_type", "file_name"].some(k => file[k] !== undefined && (typeof file[k] !== "string" || file[k].length > 512)))
throw deny();
let url;
try {
url = new URL(file.download_url);
}
catch {
throw deny();
}
const origins = options.allowedOrigins ?? (process.env.RECEIZ_ATTACHMENT_DOWNLOAD_ORIGINS ?? "").split(",").map(v => v.trim()).filter(Boolean);
// Exact HTTPS origins, not suffix matching or model-supplied allowlists. No
// redirects, cookies, OAuth headers, content conversion or custody access.
if (url.protocol !== "https:" || url.username || url.password || url.hash || !origins.includes(url.origin))
throw deny();
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT_MS);
let bytes;
try {
const response = await (options.fetch ?? globalThis.fetch)(url.href, {
signal: controller.signal, redirect: "error", credentials: "omit", cache: "no-store",
});
if ([401, 403, 404, 410].includes(response.status))
throw new ArtifactInputError("attachment_unavailable", "download");
if (!response.ok || response.redirected || (response.url && response.url !== url.href))
throw new ArtifactInputError("attachment_download_failed", "download");
const length = response.headers.get("content-length");
if (length && Number(length) > MAX_BYTES)
throw new ArtifactInputError("attachment_too_large", "download");
const reader = response.body?.getReader();
if (!reader)
throw new ArtifactInputError("attachment_unavailable", "download");
const chunks = [];
let total = 0;
try {
for (;;) {
const next = await reader.read();
if (next.done)
break;
total += next.value.byteLength;
if (total > MAX_BYTES)
throw new ArtifactInputError("attachment_too_large", "download");
chunks.push(next.value);
}
if (!total)
throw new ArtifactInputError("attachment_unavailable", "download");
bytes = Buffer.concat(chunks, total);
}
finally {
await reader.cancel().catch(() => { });
reader.releaseLock();
}
}
catch (error) {
if (error instanceof ArtifactInputError)
throw error;
throw new ArtifactInputError(controller.signal.aborted ? "attachment_download_timeout" : "attachment_download_failed", "download");
}
finally {
clearTimeout(timer);
controller.abort();
}
const transport = { byteLength: bytes.byteLength, sha256: createHash("sha256").update(bytes).digest("hex"), authority: false };
// Filename is a parser hint, never a path. MIME claims do not establish validity.
const filename = typeof file.file_name === "string" ? file.file_name.split(/[\\/]/).pop() || "attachment" : "attachment";
return { bytes, filename, mimeType: typeof file.mime_type === "string" ? file.mime_type : undefined, transport };
}
export async function verifyUploadedArtifact(raw, options = {}) {
const { bytes, filename, transport } = await readAuthorizedUploadedArtifact(raw, options);
try {
const verification = await verifyReceizOfflineSealedFile({ bytes, filename });
return { verification, verdictObtained: true, transport, mcpAuthority: false };
}
catch {
throw new ArtifactInputError("artifact_verification_unavailable", "canonical_verification");
}
}
//# sourceMappingURL=uploadedArtifact.js.map