@receiz/mcp-server
Version:
MCP server for agents building and verifying Receiz proof-native applications through the canonical Receiz SDK rails.
121 lines • 7.8 kB
JavaScript
import { createHash, randomUUID } from "node:crypto";
import { mkdir, lstat, open, readFile, realpath } from "node:fs/promises";
import { join, relative, sep } from "node:path";
import { deriveReceizSubjectIdV122, readReceizIdentityPublicBinding, readReceizTemporalSubjectSourceV125, receizBase64UrlEncode, verifyReceizArtifact, } from "@receiz/sdk";
import { createReceizMcpDeviceAiSubject } from "./deviceAiSubject.js";
const hosts = new WeakMap();
export const RECEIZ_DEVICE_AI_SUBJECT_TOOL = Object.freeze({
name: "receiz_device_ai_subject_create",
description: "Create a distinct private AI subject proof object owned by the admitted device identity. Preview first, then explicitly confirm the exact retained digest. Seals a new source and modern temporal subject with the complete canonical KaiSigil proof; saves both exact artifacts privately in the workspace. No public append. This modern V122/V125 source is separate from the historical V120 local runtime. MCP is not authority; the verified enclosing Receiz proof object and admitted owner identity remain authority.",
inputSchema: { type: "object", additionalProperties: false,
properties: { name: { type: "string", minLength: 1, maxLength: 200 },
confirmation: { type: "string", minLength: 1, description: "Single-use confirmationDigest from the preview with the same name." } } },
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
});
/** Host-only custody binding; model input cannot choose owner, sealer or path. */
export async function bindReceizMcpDeviceAiSubjectCreation(runtime, input) {
if (hosts.has(runtime))
throw new TypeError("RECEIZ_AI_SUBJECT_HOST_ALREADY_BOUND");
const { ownerIdentityAdmission, sealer, workspace } = input;
const owner = await readReceizIdentityPublicBinding(ownerIdentityAdmission);
if (!owner.signatureVerified || !owner.artifactOwnerReceizId)
throw new TypeError("RECEIZ_AI_SUBJECT_OWNER_REQUIRED");
const workspacePath = await realpath(workspace);
// Recheck after async work so competing bindings cannot replace the owner.
if (hosts.has(runtime))
throw new TypeError("RECEIZ_AI_SUBJECT_HOST_ALREADY_BOUND");
hosts.set(runtime, Object.freeze({ ownerIdentityAdmission, sealer, workspace: workspacePath,
ownerReceizId: owner.artifactOwnerReceizId, plans: new Map() }));
}
async function privateDirectory(path, create) {
if (create) {
try {
await mkdir(path, { mode: 0o700 });
}
catch (error) {
if (error.code !== "EEXIST")
throw error;
}
}
const stat = await lstat(path);
if (!stat.isDirectory() || stat.isSymbolicLink() || (process.platform !== "win32" && (stat.mode & 0o077)))
throw new TypeError("RECEIZ_AI_SUBJECT_PRIVATE_DIRECTORY_REQUIRED");
}
async function persistArtifact(path, artifact) {
const bytes = new Uint8Array(Buffer.from(artifact.exactBytesB64u, "base64url"));
const file = await open(path, "wx", 0o600);
try {
await file.writeFile(bytes);
await file.sync();
}
finally {
await file.close();
}
const readback = new Uint8Array(await readFile(path));
if (!Buffer.from(readback).equals(Buffer.from(bytes)))
throw new TypeError("RECEIZ_AI_SUBJECT_READBACK_MISMATCH");
const verification = await verifyReceizArtifact(new File([readback], artifact.filename, { type: artifact.mimeType }));
if (verification.status !== "verified-artifact" || verification.artifactDigest.value !== artifact.artifactSha256
|| verification.payloadDigest.value !== artifact.payloadSha256)
throw new TypeError("RECEIZ_AI_SUBJECT_READBACK_VERIFICATION_REQUIRED");
return Object.freeze({ ...artifact, exactBytesB64u: receizBase64UrlEncode(readback) });
}
export async function callReceizMcpDeviceAiSubjectCreate(runtime, raw = {}) {
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
throw new TypeError("RECEIZ_AI_SUBJECT_ARGUMENTS_INVALID");
const args = raw;
if (Object.keys(args).some(key => key !== "name" && key !== "confirmation"))
throw new TypeError("RECEIZ_AI_SUBJECT_ARGUMENTS_INVALID");
const name = args.name === undefined ? "Receiz AI" : args.name;
const confirmation = args.confirmation;
if (typeof name !== "string" || !name.trim() || name.length > 200
|| (confirmation !== undefined && (typeof confirmation !== "string" || !confirmation)))
throw new TypeError("RECEIZ_AI_SUBJECT_ARGUMENTS_INVALID");
const host = hosts.get(runtime);
if (!host)
return Object.freeze({ status: "unavailable", code: "RECEIZ_AI_SUBJECT_DEVICE_HOST_REQUIRED", writesPerformed: 0,
required: "Admitted device identity and its existing owner-bound offline sealer." });
if (confirmation === undefined) {
const confirmationDigest = createHash("sha256").update(`receiz.ai-subject.consent:${randomUUID()}:${name}`).digest("hex");
host.plans.set(confirmationDigest, name);
return Object.freeze({ status: "planned", name, ownerReceizId: host.ownerReceizId, visibility: "private", confirmationDigest,
confirmationRequired: true, writesPerformed: 0, modernProtocol: "V122/V125", publicAppend: false });
}
if (host.plans.get(confirmation) !== name)
throw new TypeError("RECEIZ_AI_SUBJECT_CONFIRMATION_INVALID");
host.plans.delete(confirmation);
const created = await createReceizMcpDeviceAiSubject({ ownerIdentityAdmission: host.ownerIdentityAdmission, sealer: host.sealer, name });
const parent = join(host.workspace, "receiz-ai");
await privateDirectory(parent, true);
const directory = join(parent, randomUUID());
await mkdir(directory, { mode: 0o700 });
await privateDirectory(parent, false);
await privateDirectory(directory, false);
if (await realpath(parent) !== parent || await realpath(directory) !== directory)
throw new TypeError("RECEIZ_AI_SUBJECT_PRIVATE_DIRECTORY_REQUIRED");
const sourcePath = join(directory, "source.receized.json");
const temporalPath = join(directory, "subject.receized.json");
const source = await persistArtifact(sourcePath, created.sourceArtifact);
const temporalArtifact = await persistArtifact(temporalPath, created.temporalArtifact);
const reopened = await readReceizTemporalSubjectSourceV125(temporalArtifact);
if (reopened.source.subjectState.subjectId !== created.subjectId
|| created.subjectId !== await deriveReceizSubjectIdV122(source.artifactSha256)
|| reopened.source.subjectState.ownerReceizId !== host.ownerReceizId
|| reopened.source.subjectState.stateDigest !== created.state.stateDigest)
throw new TypeError("RECEIZ_AI_SUBJECT_READBACK_BINDING_MISMATCH");
const dir = await open(directory, "r");
try {
await dir.sync();
}
finally {
await dir.close();
}
return Object.freeze({ status: "created", name, subjectId: created.subjectId, ownerReceizId: created.ownerReceizId,
visibility: "private", sourcePath, temporalPath, sourceArtifactSha256: source.artifactSha256,
sourceReference: `receiz:file:${source.artifactSha256}:${relative(host.workspace, sourcePath).split(sep).join("/")}`,
temporalReference: `receiz:file:${temporalArtifact.artifactSha256}:${relative(host.workspace, temporalPath).split(sep).join("/")}`,
temporalArtifactSha256: temporalArtifact.artifactSha256, stateDigest: reopened.source.subjectState.stateDigest,
temporalHead: reopened.head, verified: true, modernProtocol: "V122/V125", publicAppend: false,
historicalV120RuntimeAdmission: false });
}
//# sourceMappingURL=deviceAiSubjectTool.js.map