pi-mcp-extension
Version:
Full-featured, lightweight MCP extension for the Pi coding agent: stdio, SSE & Streamable HTTP transports, OAuth 2.1 (PKCE + DCR), lazy/eager lifecycles — minimal dependencies, zero bloat.
64 lines (54 loc) • 2.1 kB
text/typescript
/**
* Pure lock-record and staleness policy for pi-mcp's cross-process OAuth
* authorization lock. No filesystem, clock, or process access.
*/
import { z } from "zod";
// ─── Constants & Schema ───────────────────────────────────────────────────────
export const HEARTBEAT_MS = 30_000;
export const STALE_MS = 90_000; // 3 × 30s heartbeat
export const LockRecordSchema = z.object({
v: z.literal(1),
pid: z.number().int().positive(),
hostname: z.string().min(1),
nonce: z.string().min(1),
createdAt: z.number(),
});
export type LockRecord = z.output<typeof LockRecordSchema>;
// ─── Pure Staleness Policy ────────────────────────────────────────────────────
export function parseLockRecord(raw: string): LockRecord | null {
if (!raw) return null;
try {
const parsed: unknown = JSON.parse(raw);
const result = LockRecordSchema.safeParse(parsed);
return result.success ? result.data : null;
} catch {
// WHY: JSON.parse on a string throws SyntaxError if the lock file is corrupt or
// partially written. LockRecordSchema.safeParse does not throw. Unparseable lock
// files map to null so isLockStale() can fall back to mtime-based staleness.
return null;
}
}
/**
* Pure policy determining whether an existing lock file is stale and safe to reclaim.
*/
export function isLockStale(
record: LockRecord | null,
mtimeMs: number,
now: number,
self: { hostname: string; isPidAlive: (pid: number) => boolean },
): boolean {
if (now - mtimeMs > STALE_MS) {
return true;
}
if (record === null) {
return false;
}
if (record.hostname === self.hostname && !self.isPidAlive(record.pid)) {
return true;
}
return false;
}
export function formatHolderDetails(record: LockRecord | null): string | undefined {
if (!record) return undefined;
return `pid ${record.pid} on ${record.hostname}`;
}