/** * Mandu MCP 서버 참조 헬퍼 🔗 * * MCP 서버 설정에 메타데이터를 부착하는 편의 함수들 * * @example * ```typescript * import { z } from "zod"; * import { mcpServerRef, sensitiveToken, envValue } from "./mcp-ref"; * * const mcpConfigSchema = z.object({ * sequential: mcpServerRef("sequential-thinking"), * apiKey: sensitiveToken(), * baseUrl: envValue("API_BASE_URL", "http://localhost:3000"), * }); * ``` */ import { z } from "zod"; import { withMetadata, withMetadataMultiple, getMetadata, hasMetadata } from "./metadata.js"; import { SCHEMA_REFERENCE, SENSITIVE_FIELD, FIELD_SOURCE, PROTECTED_FIELD, MCP_SERVER_STATUS, RUNTIME_INJECTED, type SchemaReferenceMetadata, type SensitiveFieldMetadata, type FieldSourceMetadata, type ProtectedFieldMetadata, type McpServerStatusMetadata, } from "./symbols.js"; // ============================================ // MCP 서버 참조 // ============================================ /** * MCP 서버 참조 스키마 생성 * * @param serverName 참조할 MCP 서버 이름 * @param optional 선택적 참조 여부 * * @example * ```typescript * const schema = z.object({ * mcpServer: mcpServerRef("sequential-thinking"), * }); * ``` */ export function mcpServerRef( serverName: string, optional: boolean = false ): z.ZodString { const schema = z.string(); return withMetadata(schema, SCHEMA_REFERENCE, { type: "mcpServer", name: serverName, optional, } satisfies SchemaReferenceMetadata); } /** * MCP 서버 상태를 포함한 참조 스키마 */ export function mcpServerWithStatus( serverName: string, initialStatus: McpServerStatusMetadata["status"] = "unknown" ): z.ZodString { const schema = z.string(); return withMetadataMultiple(schema, [ [ SCHEMA_REFERENCE, { type: "mcpServer", name: serverName, optional: false, } satisfies SchemaReferenceMetadata, ], [ MCP_SERVER_STATUS, { status: initialStatus, } satisfies McpServerStatusMetadata, ], ]); } // ============================================ // 민감 정보 스키마 // ============================================ /** * 민감한 토큰/시크릿 스키마 * * @example * ```typescript * const schema = z.object({ * apiKey: sensitiveToken(), * password: sensitiveToken("password"), * }); * ``` */ export function sensitiveToken( fieldName?: string ): z.ZodString { const schema = z.string(); return withMetadataMultiple(schema, [ [ SENSITIVE_FIELD, { redactIn: ["log", "diff", "snapshot"], mask: "***", } satisfies SensitiveFieldMetadata, ], [ PROTECTED_FIELD, { reason: `Sensitive ${fieldName ?? "token"} - should not be modified by AI`, allowedModifiers: ["human"], } satisfies ProtectedFieldMetadata, ], ]); } /** * 선택적 민감 토큰 스키마 */ export function optionalSensitiveToken(): z.ZodOptional { return sensitiveToken().optional(); } // ============================================ // 환경 변수 스키마 // ============================================ /** * 환경 변수에서 오는 값 스키마 * * @param envKey 환경 변수 키 * @param defaultValue 기본값 * * @example * ```typescript * const schema = z.object({ * port: envValue("PORT", "3000").transform(Number), * apiUrl: envValue("API_URL"), * }); * ``` */ export function envValue( envKey: string, defaultValue?: string ): z.ZodString { const schema = z.string(); return withMetadata(schema, FIELD_SOURCE, { source: "env", key: envKey, fallback: defaultValue, } satisfies FieldSourceMetadata); } /** * 민감한 환경 변수 스키마 (토큰, 비밀키 등) */ export function sensitiveEnvValue(envKey: string): z.ZodString { const schema = z.string(); return withMetadataMultiple(schema, [ [ FIELD_SOURCE, { source: "env", key: envKey, } satisfies FieldSourceMetadata, ], [ SENSITIVE_FIELD, { redactIn: ["log", "diff", "snapshot"], mask: "***", } satisfies SensitiveFieldMetadata, ], ]); } // ============================================ // 보호된 필드 스키마 // ============================================ /** * AI 수정 불가 필드 스키마 * * @param reason 보호 이유 * * @example * ```typescript * const schema = z.object({ * securityLevel: protectedField("Security configuration"), * }); * ``` */ export function protectedField(reason: string): z.ZodString { const schema = z.string(); return withMetadata(schema, PROTECTED_FIELD, { reason, allowedModifiers: ["human"], } satisfies ProtectedFieldMetadata); } /** * 숫자 타입의 보호된 필드 */ export function protectedNumber(reason: string): z.ZodNumber { const schema = z.number(); return withMetadata(schema, PROTECTED_FIELD, { reason, allowedModifiers: ["human"], } satisfies ProtectedFieldMetadata); } /** * 불리언 타입의 보호된 필드 */ export function protectedBoolean(reason: string): z.ZodBoolean { const schema = z.boolean(); return withMetadata(schema, PROTECTED_FIELD, { reason, allowedModifiers: ["human"], } satisfies ProtectedFieldMetadata); } // ============================================ // 런타임 주입 스키마 // ============================================ /** * 런타임에 주입되는 값 스키마 * * @example * ```typescript * const schema = z.object({ * requestId: runtimeInjected(z.string()), * userId: runtimeInjected(z.string().optional()), * }); * ``` */ export function runtimeInjected(schema: T): T { return withMetadata(schema, RUNTIME_INJECTED, true); } // ============================================ // 복합 스키마 헬퍼 // ============================================ /** * MCP 서버 설정 스키마 생성 * * @example * ```typescript * const mcpServerSchema = createMcpServerSchema(); * // { command: string, args?: string[], env?: Record } * ``` */ export function createMcpServerSchema() { return z.object({ /** 실행 명령어 */ command: z.string(), /** 명령어 인자 */ args: z.array(z.string()).optional(), /** 환경 변수 */ env: z.record(z.string()).optional(), /** 서버 URL (stdio 대신 HTTP 사용 시) */ url: z.string().url().optional(), /** 버전 */ version: z.string().optional(), }); } /** * 민감 정보가 포함된 MCP 서버 설정 스키마 */ export function createMcpServerSchemaWithSecrets() { return z.object({ command: z.string(), args: z.array(z.string()).optional(), env: z .record( withMetadata(z.string(), SENSITIVE_FIELD, { redactIn: ["log", "diff"], }) ) .optional(), url: z.string().url().optional(), token: sensitiveToken("MCP server token").optional(), apiKey: sensitiveToken("MCP server API key").optional(), }); } // ============================================ // 유틸리티 // ============================================ /** * 스키마가 MCP 서버 참조인지 확인 */ export function isMcpServerRef(schema: z.ZodType): boolean { const ref = getMetadata(schema, SCHEMA_REFERENCE); return ref?.type === "mcpServer"; } /** * 스키마에서 MCP 서버 이름 추출 */ export function getMcpServerName(schema: z.ZodType): string | undefined { const ref = getMetadata(schema, SCHEMA_REFERENCE); return ref?.type === "mcpServer" ? ref.name : undefined; } /** * 스키마가 민감 필드인지 확인 */ export function isSensitiveField(schema: z.ZodType): boolean { return hasMetadata(schema, SENSITIVE_FIELD); } /** * 스키마가 보호된 필드인지 확인 */ export function isProtectedField(schema: z.ZodType): boolean { return hasMetadata(schema, PROTECTED_FIELD); } /** * 스키마가 환경 변수 기반인지 확인 */ export function isEnvBasedField(schema: z.ZodType): boolean { const source = getMetadata(schema, FIELD_SOURCE); return source?.source === "env"; }