Files
openproject-mcp/src/config/index.ts
T

100 lines
2.9 KiB
TypeScript

import { z } from "zod";
/**
* Zod schema for OpenProject MCP server configuration.
*/
export const configSchema = z
.object({
baseUrl: z
.string({ required_error: "OPENPROJECT_BASE_URL is required" })
.url("OPENPROJECT_BASE_URL must be a valid URL")
.transform((val) => val.replace(/\/+$/, "")),
apiKey: z
.string()
.min(1, "OPENPROJECT_API_KEY must not be empty")
.optional(),
readOnly: z.boolean().default(false),
port: z.number().int().min(0).max(65535).optional(),
host: z.string().optional(),
})
.superRefine((data, ctx) => {
if (data.port === undefined && (!data.apiKey || data.apiKey.trim().length === 0)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "OPENPROJECT_API_KEY is required in stdio mode (when port is not set)",
path: ["apiKey"],
});
}
});
export type AppConfig = z.infer<typeof configSchema>;
/**
* Helper to parse a CLI argument flag that may take a value either as
* `--flag value` or `--flag=value`.
*/
function parseCliArg(argv: string[], flag: string): string | undefined {
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
if (!arg) continue;
if (arg === flag) {
const nextArg = argv[i + 1];
if (nextArg !== undefined && !nextArg.startsWith("-")) {
return nextArg;
}
} else if (arg.startsWith(`${flag}=`)) {
return arg.slice(flag.length + 1);
}
}
return undefined;
}
/**
* Parses and validates configuration from environment variables and CLI arguments.
*
* @param env - Environment variable map (defaults to process.env)
* @param argv - CLI arguments array (defaults to process.argv)
* @returns Validated AppConfig object
*/
export function loadConfig(
env: Record<string, string | undefined> = process.env,
argv: string[] = process.argv
): AppConfig {
const isReadOnlyArg = argv.includes("--read-only");
const readOnlyEnv = env.OPENPROJECT_READ_ONLY?.trim().toLowerCase();
const isReadOnly =
isReadOnlyArg ||
readOnlyEnv === "true" ||
readOnlyEnv === "1" ||
readOnlyEnv === "yes";
const portArg = parseCliArg(argv, "--port");
const portStr = portArg ?? env.PORT;
let port: number | undefined;
if (portStr !== undefined && portStr.trim() !== "") {
const parsed = parseInt(portStr.trim(), 10);
if (Number.isNaN(parsed)) {
throw new Error(`Invalid port value: ${portStr}`);
}
port = parsed;
}
const hostArg = parseCliArg(argv, "--host");
const hostStr = hostArg ?? env.HOST;
const host = hostStr !== undefined && hostStr.trim() !== "" ? hostStr.trim() : undefined;
const apiKeyRaw = env.OPENPROJECT_API_KEY?.trim();
const apiKey = apiKeyRaw !== undefined && apiKeyRaw.length > 0 ? apiKeyRaw : undefined;
return configSchema.parse({
baseUrl: env.OPENPROJECT_BASE_URL,
apiKey,
readOnly: isReadOnly,
port,
host,
});
}
export const parseConfig = loadConfig;