@receiz/mcp-server
Version:
MCP server for agents building and verifying Receiz proof-native applications through the canonical Receiz SDK rails.
236 lines • 13.4 kB
JavaScript
import { randomUUID, randomBytes } from "node:crypto";
import { mkdir, lstat, open, readdir, readFile, unlink, rename } from "node:fs/promises";
import { isAbsolute, join } from "node:path";
import { handleReceizMcpMessage } from "./index.js";
import { deviceRuntimeOwner, deviceRuntimeRelaySigner } from "./deviceOwner.js";
import { bindDeviceRelayWorkspace } from "./deviceWorkspace.js";
const uuid = /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/;
/** Carry the relay's own attempt locator with an app-only proof chunk. The
* locator lets the first-party Receiz route re-read the held exact bytes; it
* never authorizes sealing, verification, or proof admission. */
export function attachReceizChunkAttempt(request, response, attemptId) {
const params = request.params;
if (request.method !== "tools/call" || !params || typeof params !== "object"
|| params.name !== "receiz_offline_artifact_chunk")
return response;
const result = response.result;
if (!result || typeof result !== "object" || Array.isArray(result) || result.isError)
return response;
const toolResult = result;
const publicPart = toolResult.structuredContent;
if (!publicPart || typeof publicPart !== "object" || Array.isArray(publicPart))
return response;
const meta = toolResult._meta;
const privatePart = meta && typeof meta === "object" && !Array.isArray(meta)
? meta["receiz/sealedChunk"] : null;
const privateBytes = privatePart && typeof privatePart === "object" && !Array.isArray(privatePart)
? privatePart.bytesBase64 : null;
if (typeof publicPart.bytesBase64 !== "string" && typeof privateBytes !== "string")
return response;
toolResult.structuredContent = { ...publicPart, receizTransportAttemptId: attemptId };
return response;
}
/** Device-side transport. The owner is read from a genuine SDK admission, never
* from a tool argument or OAuth claim. Completed replies retry; execution does not. */
export async function runReceizMcpDeviceRelay(options) {
const { runtime, accessToken, directory, signal, onError } = options;
const refresh = options.refresh ? Object.freeze({ ...options.refresh }) : undefined;
const fetcher = options.fetch ?? globalThis.fetch;
const endpoint = new URL(options.endpoint);
if (endpoint.protocol !== "https:" || endpoint.username || endpoint.password || endpoint.search || endpoint.hash || !accessToken || !isAbsolute(directory))
throw Error("RECEIZ_DEVICE_RELAY_CONFIG_INVALID");
const owner = deviceRuntimeOwner(runtime);
const transportOwner = options.transportOwnerId ?? owner;
if (!transportOwner || transportOwner !== transportOwner.trim())
throw Error("RECEIZ_DEVICE_RELAY_CONFIG_INVALID");
const signRequest = deviceRuntimeRelaySigner(runtime);
await mkdir(directory, { recursive: true, mode: 0o700 });
const stat = await lstat(directory);
if (!stat.isDirectory() || (process.platform !== "win32" && (stat.mode & 0o077)))
throw Error("RECEIZ_DEVICE_PRIVATE_CUSTODY_REQUIRED");
if (refresh && (!refresh.refreshToken || !refresh.clientId || refresh.clientId.length > 200))
throw Error("RECEIZ_DEVICE_RELAY_CONFIG_INVALID");
const resource = `${endpoint.origin}/api/mcp/device`;
const tokenEndpoint = new URL("/api/oidc/token", endpoint.origin);
const credentialsPath = join(directory, "oauth-transport.json");
let credentials = refresh ? { ownerId: owner, transportOwnerId: transportOwner, resource, clientId: refresh.clientId, accessToken, refreshToken: refresh.refreshToken } : undefined;
const validToken = (value) => typeof value === "string" && value.length > 0 && value.length <= 32_000 && !/[\s\u0000-\u001f]/.test(value);
if (refresh) {
try {
const file = await lstat(credentialsPath);
if (!file.isFile() || file.size > 70_000 || (process.platform !== "win32" && (file.mode & 0o077)))
throw Error("RECEIZ_DEVICE_PRIVATE_CUSTODY_REQUIRED");
const saved = JSON.parse(await readFile(credentialsPath, "utf8"));
if (saved.ownerId !== owner || (saved.transportOwnerId ?? saved.ownerId) !== transportOwner || saved.resource !== resource || saved.clientId !== refresh.clientId)
throw Error("RECEIZ_DEVICE_RELAY_IDENTITY_MISMATCH");
if (!validToken(saved.accessToken) || !validToken(saved.refreshToken))
throw Error("RECEIZ_DEVICE_RELAY_AUTH_STORAGE_FAILED");
credentials = saved;
}
catch (error) {
if (error.code !== "ENOENT") {
if (error instanceof Error && /^RECEIZ_DEVICE_[A-Z_]+$/.test(error.message))
throw error;
throw Error("RECEIZ_DEVICE_RELAY_AUTH_STORAGE_FAILED");
}
}
}
const readResponse = async (response) => {
// Server responses are bounded independently of claimed Content-Length.
const reader = response.body?.getReader();
if (!reader)
throw Error("RECEIZ_DEVICE_RELAY_RESPONSE_INVALID");
const chunks = [];
let count = 0;
try {
for (;;) {
const item = await reader.read();
if (item.done)
break;
count += item.value.length;
if (count > 1024 * 1024) {
await reader.cancel();
throw Error("RECEIZ_DEVICE_RELAY_ENVELOPE_LIMIT");
}
chunks.push(item.value);
}
}
finally {
reader.releaseLock();
}
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
};
const refreshAccessToken = async () => {
if (!refresh || !credentials)
throw Error("RECEIZ_DEVICE_RELAY_AUTH_REQUIRED");
// Compose the existing resource-bound refresh grant. The endpoint, audience
// and requested transport role are pinned by this host, never a challenge.
const form = new URLSearchParams({ grant_type: "refresh_token", client_id: refresh.clientId,
refresh_token: credentials.refreshToken, resource, scope: "openid offline_access mcp:device" });
if (refresh.clientSecret)
form.set("client_secret", refresh.clientSecret);
const response = await fetcher(tokenEndpoint, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" },
body: form.toString(), signal: AbortSignal.any([signal, AbortSignal.timeout(15_000)]), redirect: "error" });
if (!response.ok)
throw Error(response.status < 500 ? "RECEIZ_DEVICE_RELAY_AUTH_REQUIRED" : "RECEIZ_DEVICE_RELAY_TRANSPORT_UNAVAILABLE");
const result = await readResponse(response);
const scopes = typeof result.scope === "string" ? result.scope.split(/\s+/).filter(Boolean) : [];
if (result.token_type !== "Bearer" || !validToken(result.access_token) || !validToken(result.refresh_token)
|| typeof result.expires_in !== "number" || !Number.isFinite(result.expires_in) || result.expires_in <= 0
|| !["openid", "offline_access", "mcp:device"].every(scope => scopes.includes(scope))
|| scopes.some(scope => !["openid", "offline_access", "mcp:device"].includes(scope)))
throw Error("RECEIZ_DEVICE_RELAY_AUTH_REQUIRED");
const next = { ownerId: owner, transportOwnerId: transportOwner, resource, clientId: refresh.clientId, accessToken: result.access_token, refreshToken: result.refresh_token };
const pending = join(directory, `.oauth-${randomUUID()}.pending`);
try {
const file = await open(pending, "wx", 0o600);
try {
await file.writeFile(JSON.stringify(next));
await file.sync();
}
finally {
await file.close();
}
await rename(pending, credentialsPath);
// Persist the rotated secret before sending another relay request.
if (process.platform !== "win32") {
const folder = await open(directory, "r");
try {
await folder.sync();
}
finally {
await folder.close();
}
}
}
catch {
throw Error("RECEIZ_DEVICE_RELAY_AUTH_STORAGE_FAILED");
}
finally {
await unlink(pending).catch(() => undefined);
}
credentials = next;
};
const post = async (body) => {
const bodyText = JSON.stringify(body);
const proofHeaders = await signRequest({ transportOwnerId: transportOwner, bodyText, actionId: randomBytes(32).toString("hex") });
const send = () => fetcher(endpoint, { method: "POST", headers: { ...proofHeaders, "content-type": "application/json", authorization: `Bearer ${credentials?.accessToken ?? accessToken}` },
body: bodyText, signal: AbortSignal.any([signal, AbortSignal.timeout(15_000)]), redirect: "error" });
let response = await send();
if (response.status === 401 && refresh) {
await response.body?.cancel();
await refreshAccessToken();
response = await send();
}
if (!response.ok)
throw Error(response.status === 401 || response.status === 403 ? "RECEIZ_DEVICE_RELAY_AUTH_REQUIRED" : "RECEIZ_DEVICE_RELAY_TRANSPORT_UNAVAILABLE");
return readResponse(response);
};
const flush = async () => {
for (const name of await readdir(directory)) {
if (!name.endsWith(".json") || !uuid.test(name.slice(0, -5)))
continue;
const path = join(directory, name);
const file = await lstat(path);
if (!file.isFile() || (process.platform !== "win32" && (file.mode & 0o077)))
throw Error("RECEIZ_DEVICE_PRIVATE_CUSTODY_REQUIRED");
const reply = JSON.parse(await readFile(path, "utf8"));
if (`${reply.attemptId}.json` !== name || !uuid.test(reply.workerAttempt))
throw Error("RECEIZ_DEVICE_RELAY_JOURNAL_INVALID");
if (reply.ownerId !== owner || (reply.transportOwnerId ?? reply.ownerId) !== transportOwner)
throw Error("RECEIZ_DEVICE_RELAY_IDENTITY_MISMATCH");
const result = await post({ action: "complete", ...reply, transportOwnerId: transportOwner });
if (result.accepted !== true)
throw Error("RECEIZ_DEVICE_RELAY_RESPONSE_INVALID");
await unlink(path);
}
};
while (!signal.aborted) {
try {
await flush();
const workerAttempt = randomUUID();
const reply = await post({ action: "claim", workerAttempt, ownerId: owner, transportOwnerId: transportOwner });
if (reply.ownerId !== owner || (reply.transportOwnerId ?? reply.ownerId) !== transportOwner)
throw Error("RECEIZ_DEVICE_RELAY_IDENTITY_MISMATCH");
if (reply.job !== null) {
const job = reply.job;
if (!job || !uuid.test(job.attemptId) || job.ownerId !== transportOwner || job.expiresAt <= Date.now() || job.request?.id === undefined)
throw Error("RECEIZ_DEVICE_RELAY_REQUEST_INVALID");
let result;
try {
const request = await bindDeviceRelayWorkspace(job.request, runtime.localCustody?.workspace ?? directory);
result = attachReceizChunkAttempt(request, await handleReceizMcpMessage(request, runtime), job.attemptId);
}
catch {
result = { jsonrpc: "2.0", id: job.request.id, error: { code: -32603, message: "Device execution failed; inspect held proof state before repeating this attempt." } };
}
const pending = join(directory, `${job.attemptId}.pending`);
const file = await open(pending, "wx", 0o600);
try {
await file.writeFile(JSON.stringify({ ownerId: owner, transportOwnerId: transportOwner, attemptId: job.attemptId, workerAttempt, response: result }));
await file.sync();
}
finally {
await file.close();
}
await rename(pending, join(directory, `${job.attemptId}.json`));
await flush();
}
}
catch (error) {
if (signal.aborted)
break;
const code = error instanceof Error && /^RECEIZ_DEVICE_[A-Z_]+$/.test(error.message) ? error.message : "RECEIZ_DEVICE_RELAY_TRANSPORT_UNAVAILABLE";
onError?.(code);
if (code === "RECEIZ_DEVICE_RELAY_AUTH_REQUIRED" || code === "RECEIZ_DEVICE_RELAY_IDENTITY_MISMATCH" || code === "RECEIZ_DEVICE_RELAY_AUTH_STORAGE_FAILED")
throw Error(code);
}
if (!signal.aborted)
await new Promise(resolve => {
const done = () => { clearTimeout(timer); signal.removeEventListener("abort", done); resolve(); };
const timer = setTimeout(done, 1000);
signal.addEventListener("abort", done, { once: true });
});
}
}
//# sourceMappingURL=deviceRelay.js.map