UNPKG

@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
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