@receiz/mcp-server
Version:
MCP server for agents building and verifying Receiz proof-native applications through the canonical Receiz SDK rails.
112 lines • 6 kB
JavaScript
import { createHash } from "node:crypto";
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 this when verifying an original user-selected ChatGPT attachment. Retrieve its authorized original bytes and run the canonical Receiz verifier locally. 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", 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);
/** 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") || !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