jortt-mcp/src/config.ts
Claude a73af353d4 Harden server; add authenticated Streamable HTTP transport and Nix module
- Streamable HTTP transport (MCP_TRANSPORT=http) with mandatory auth:
  static bearer tokens or OIDC resource-server mode (JWT via JWKS, or
  RFC 7662 introspection), RFC 9728 protected-resource metadata, and
  DNS-rebinding protection
- secrets loadable from files via *_FILE variants
- timeouts on all outbound requests; JORTT_READ_ONLY tool filtering
- OpenAPI spec committed and bundled; no implicit network fetches
- Nix flake: sandboxed package build + hardened NixOS module taking an
  EnvironmentFile (agenix-friendly)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:10:55 +02:00

50 lines
1.6 KiB
TypeScript

import { readFileSync } from "node:fs";
/**
* Read a configuration value from the environment, with `<NAME>_FILE`
* indirection: if `<NAME>_FILE` is set, the value is read from that file
* (trailing whitespace stripped). This keeps secrets out of process
* environments and lets systemd credentials / agenix-managed files be used
* directly.
*/
export function envOrFile(name: string): string | undefined {
const direct = process.env[name];
const file = process.env[`${name}_FILE`];
if (direct && file) {
throw new Error(`Both ${name} and ${name}_FILE are set; use only one.`);
}
if (file) {
try {
return readFileSync(file, "utf8").replace(/\s+$/, "");
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
throw new Error(`Could not read ${name}_FILE (${file}): ${message}`);
}
}
return direct || undefined;
}
/** Like envOrFile, but exits with a helpful message when unset. */
export function requireEnvOrFile(name: string, hint: string): string {
const value = envOrFile(name);
if (!value) {
console.error(`[jortt-mcp] Missing required configuration: ${name}\n${hint}`);
process.exit(1);
}
return value;
}
export function envFlag(name: string): boolean {
const value = process.env[name]?.toLowerCase();
return value === "1" || value === "true" || value === "yes";
}
/** Parse a comma- or whitespace-separated env list. */
export function envList(name: string): string[] {
const value = process.env[name];
if (!value) return [];
return value
.split(/[,\s]+/)
.map((s) => s.trim())
.filter(Boolean);
}