Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions lib/auth/mcp-keys.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import "server-only";
import { createHmac } from "node:crypto";
import { mcpPepperStatus, type McpPepperStatus } from "./mcp-pepper";
import { mcpPepperStatus, type McpPepperStatus } from "./mcp-pepper.mjs";
import { Tier, normalizeTier } from "@/prototype/fixtures";

/**
Expand Down Expand Up @@ -45,10 +45,10 @@ interface RawEntry {
*/
export const MCP_KEY_RE = /^cdk_[A-Za-z0-9_-]{43}$/;

// The pepper rule lives in ./mcp-pepper (plain TS, no server-only / alias imports) so the mint
// The pepper rule lives in ./mcp-pepper (plain JS, no imports; node 20 can load it) so the mint
// script applies exactly the same check.
export { MIN_MCP_KEY_PEPPER_LENGTH, MIN_MCP_KEY_PEPPER_DISTINCT, mcpPepperStatus, pepperStatus } from "./mcp-pepper";
export type { McpPepperStatus } from "./mcp-pepper";
export { MIN_MCP_KEY_PEPPER_LENGTH, MIN_MCP_KEY_PEPPER_DISTINCT, mcpPepperStatus, pepperStatus } from "./mcp-pepper.mjs";
export type { McpPepperStatus } from "./mcp-pepper.mjs";

/** True when MCP_API_KEYS holds at least one entry. */
export function mcpStoreConfigured(env: NodeJS.ProcessEnv = process.env): boolean {
Expand Down
5 changes: 5 additions & 0 deletions lib/auth/mcp-pepper.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export declare const MIN_MCP_KEY_PEPPER_LENGTH: number;
export declare const MIN_MCP_KEY_PEPPER_DISTINCT: number;
export type McpPepperStatus = "ok" | "missing" | "weak";
export declare function pepperStatus(raw: string | undefined | null): McpPepperStatus;
export declare function mcpPepperStatus(env?: NodeJS.ProcessEnv): McpPepperStatus;
13 changes: 5 additions & 8 deletions lib/auth/mcp-pepper.ts → lib/auth/mcp-pepper.mjs
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
/**
* MCP key pepper rule, shared by the resolver (lib/auth/mcp-keys.ts) and the mint script
* (scripts/mint-mcp-key.mjs). No server-only or path-alias imports, so plain node can load it.
*/
// MCP key pepper rule, shared by the resolver (lib/auth/mcp-keys.ts) and the mint script
// (scripts/mint-mcp-key.mjs). Plain JavaScript with no imports, so any supported node version
// (including 20) can load it; types live in mcp-pepper.d.mts.

/** Minimum length of the server-side pepper (`MCP_KEY_PEPPER`), after trimming. */
export const MIN_MCP_KEY_PEPPER_LENGTH = 32;
/** Minimum distinct characters in the pepper (rejects "aaaa…" / whitespace padding). */
export const MIN_MCP_KEY_PEPPER_DISTINCT = 8;

export type McpPepperStatus = "ok" | "missing" | "weak";

/** Status of a pepper value: set, >= 32 chars, >= 8 distinct characters, no surrounding whitespace. */
export function pepperStatus(raw: string | undefined | null): McpPepperStatus {
export function pepperStatus(raw) {
const v = typeof raw === "string" ? raw : "";
if (!v.trim()) return "missing";
const p = v.trim();
Expand All @@ -20,6 +17,6 @@ export function pepperStatus(raw: string | undefined | null): McpPepperStatus {
}

/** Whether `MCP_KEY_PEPPER` in `env` is usable. */
export function mcpPepperStatus(env: NodeJS.ProcessEnv = process.env): McpPepperStatus {
export function mcpPepperStatus(env = process.env) {
return pepperStatus(env.MCP_KEY_PEPPER);
}
3 changes: 1 addition & 2 deletions scripts/check-mcp-keys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ async function loadResolver() {
// mcp-keys.ts's only runtime import from the barrel is normalizeTier (Tier is type-only).
m = m.replace(/^import\s+\{[^}]*\}\s+from\s+["']@\/prototype\/fixtures["'];?\s*$/m,
'import { normalizeTier } from "./types.ts";');
m = m.replace(/from\s+["']\.\/mcp-pepper["']/g, 'from "./mcp-pepper.ts"');
fs.copyFileSync(path.join(ROOT, "lib/auth/mcp-pepper.ts"), path.join(tmp, "mcp-pepper.ts"));
fs.copyFileSync(path.join(ROOT, "lib/auth/mcp-pepper.mjs"), path.join(tmp, "mcp-pepper.mjs"));
fs.writeFileSync(path.join(tmp, "mcp-keys.ts"), m);
return import(path.join(tmp, "mcp-keys.ts"));
}
Expand Down
2 changes: 1 addition & 1 deletion scripts/mint-mcp-key.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// keyed by HMAC-SHA256(key, MCP_KEY_PEPPER), matching lib/auth/mcp-keys.ts.
import { createHmac, randomBytes } from "node:crypto";
import { pathToFileURL } from "node:url";
import { pepperStatus } from "../lib/auth/mcp-pepper.ts";
import { pepperStatus } from "../lib/auth/mcp-pepper.mjs";

export const MCP_KEY_PREFIX = "cdk_";
const TIERS = new Set(["public", "commercial", "academic", "confidential"]);
Expand Down
13 changes: 13 additions & 0 deletions test/mcp-key-floor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,16 @@ describe("mint script applies the resolver's pepper rule", () => {
}
});
});

describe("mint script runs on node 20 (no TypeScript imports)", () => {
it("scripts/mint-mcp-key.mjs and lib/auth/mcp-pepper.mjs import no .ts file", async () => {
const { readFileSync } = await import("node:fs");
const { join } = await import("node:path");
const root = join(__dirname, "..");
const mint = readFileSync(join(root, "scripts", "mint-mcp-key.mjs"), "utf8");
expect(mint).not.toMatch(/from\s+["'][^"']+\.(c|m)?ts["']/);
expect(mint).toMatch(/from "\.\.\/lib\/auth\/mcp-pepper\.mjs"/);
const rule = readFileSync(join(root, "lib", "auth", "mcp-pepper.mjs"), "utf8");
expect(rule).not.toMatch(/^\s*import\s/m);
});
});
Loading