feat(config): support port and host options for HTTP/SSE transport

This commit is contained in:
Lucas Winther
2026-09-06 21:22:50 +02:00
parent 2fad0036e7
commit cda83e320f
2 changed files with 223 additions and 12 deletions
+68 -12
View File
@@ -3,19 +3,52 @@ 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({ required_error: "OPENPROJECT_API_KEY is required" })
.min(1, "OPENPROJECT_API_KEY must not be empty"),
readOnly: z.boolean().default(false),
});
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.
*
@@ -23,7 +56,7 @@ export type AppConfig = z.infer<typeof configSchema>;
* @param argv - CLI arguments array (defaults to process.argv)
* @returns Validated AppConfig object
*/
export function parseConfig(
export function loadConfig(
env: Record<string, string | undefined> = process.env,
argv: string[] = process.argv
): AppConfig {
@@ -35,9 +68,32 @@ export function parseConfig(
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: env.OPENPROJECT_API_KEY,
apiKey,
readOnly: isReadOnly,
port,
host,
});
}
export const parseConfig = loadConfig;