- 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>
50 lines
1.6 KiB
TypeScript
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);
|
|
}
|