{"version":3,"sources":["jsdelivr-header.js","/npm/toolception@0.5.5/src/mode/ModeResolver.ts","/npm/toolception@0.5.5/src/mode/ModuleResolver.ts","/npm/toolception@0.5.5/src/errors/ToolingError.ts","/npm/toolception@0.5.5/src/core/ToolRegistry.ts","/npm/toolception@0.5.5/src/core/DynamicToolManager.ts","/npm/toolception@0.5.5/src/meta/registerMetaTools.ts","/npm/toolception@0.5.5/src/core/ServerOrchestrator.ts","/npm/toolception@0.5.5/src/session/ClientResourceCache.ts","/npm/toolception@0.5.5/src/http/endpointRegistration.ts","/npm/toolception@0.5.5/src/http/FastifyTransport.ts","/npm/toolception@0.5.5/src/server/createMcpServer.ts","/npm/toolception@0.5.5/src/permissions/validatePermissionConfig.ts","/npm/toolception@0.5.5/src/permissions/PermissionResolver.ts","/npm/toolception@0.5.5/src/permissions/createPermissionAwareBundle.ts","/npm/toolception@0.5.5/src/permissions/PermissionAwareFastifyTransport.ts","/npm/toolception@0.5.5/src/server/createPermissionBasedMcpServer.ts","/npm/toolception@0.5.5/src/http/customEndpoints.ts"],"names":[],"mappings":"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","file":"/npm/toolception@0.5.5/dist/index.js","sourceRoot":"","sourcesContent":["/**\n * Minified by jsDelivr using Terser v5.39.0.\n * Original file: /npm/toolception@0.5.5/dist/index.js\n *\n * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files\n */\n","import type { Mode, ToolSetCatalog } from \"../types/index.js\";\n\ninterface ModeResolverKeys {\n  dynamic?: string[]; // keys that, when present/true, enable dynamic mode\n  toolsets?: string[]; // keys that carry comma-separated toolsets\n}\n\ninterface ModeResolverOptions {\n  keys?: ModeResolverKeys;\n}\n\nconst DEFAULT_KEYS: Required<ModeResolverKeys> = {\n  dynamic: [\n    \"dynamic-tool-discovery\",\n    \"dynamicToolDiscovery\",\n    \"DYNAMIC_TOOL_DISCOVERY\",\n  ],\n  toolsets: [\"tool-sets\", \"toolSets\", \"FMP_TOOL_SETS\"],\n};\n\nexport class ToolsetValidator {\n  private readonly keys: Required<ModeResolverKeys>;\n\n  constructor(options: ModeResolverOptions = {}) {\n    this.keys = {\n      dynamic: options.keys?.dynamic ?? DEFAULT_KEYS.dynamic,\n      toolsets: options.keys?.toolsets ?? DEFAULT_KEYS.toolsets,\n    };\n  }\n\n  public resolveMode(\n    env?: Record<string, string | undefined>,\n    args?: Record<string, unknown>\n  ): Mode | null {\n    // Check args first\n    if (this.isDynamicEnabled(args)) return \"DYNAMIC\";\n\n    const toolsetsFromArgs = this.getToolsetsString(args);\n    if (toolsetsFromArgs) return \"STATIC\";\n\n    // Check env next\n    if (this.isDynamicEnabled(env)) return \"DYNAMIC\";\n\n    const toolsetsFromEnv = this.getToolsetsString(env);\n    if (toolsetsFromEnv) return \"STATIC\";\n\n    return null; // no override\n  }\n\n  public parseCommaSeparatedToolSets(\n    input: string,\n    catalog: ToolSetCatalog\n  ): string[] {\n    if (!input || typeof input !== \"string\") return [];\n    const raw = input\n      .split(\",\")\n      .map((s) => s.trim())\n      .filter((s) => s.length > 0);\n\n    const valid = new Set(Object.keys(catalog));\n    const result: string[] = [];\n    for (const name of raw) {\n      if (valid.has(name)) result.push(name);\n      else\n        console.warn(\n          `Invalid toolset '${name}' ignored. Available: ${Array.from(\n            valid\n          ).join(\", \")}`\n        );\n    }\n    return result;\n  }\n\n  public getModulesForToolSets(\n    toolsets: string[],\n    catalog: ToolSetCatalog\n  ): string[] {\n    const modules = new Set<string>();\n    for (const name of toolsets) {\n      const def = catalog[name];\n      if (!def) continue;\n      (def.modules || []).forEach((m) => modules.add(m));\n    }\n    return Array.from(modules);\n  }\n\n  public validateToolsetName(\n    name: unknown,\n    catalog: ToolSetCatalog\n  ): { isValid: boolean; sanitized?: string; error?: string } {\n    if (!name || typeof name !== \"string\") {\n      return {\n        isValid: false,\n        error: `Invalid toolset name provided. Must be a non-empty string. Available toolsets: ${Object.keys(\n          catalog\n        ).join(\", \")}`,\n      };\n    }\n    const sanitized = name.trim();\n    if (sanitized.length === 0) {\n      return {\n        isValid: false,\n        error: `Empty toolset name provided. Available toolsets: ${Object.keys(\n          catalog\n        ).join(\", \")}`,\n      };\n    }\n    if (!catalog[sanitized]) {\n      return {\n        isValid: false,\n        error: `Toolset '${sanitized}' not found. Available toolsets: ${Object.keys(\n          catalog\n        ).join(\", \")}`,\n      };\n    }\n    return { isValid: true, sanitized };\n  }\n\n  /**\n   * Validates and retrieves modules for a set of toolsets.\n   * Note: A toolset with only direct tools (no modules) is valid and returns an empty modules array.\n   * @param toolsetNames - Array of toolset names to validate\n   * @param catalog - The toolset catalog to validate against\n   * @returns Validation result with modules array if valid\n   */\n  public validateToolsetModules(\n    toolsetNames: string[],\n    catalog: ToolSetCatalog\n  ): { isValid: boolean; modules?: string[]; error?: string } {\n    try {\n      // Verify all toolset names exist in catalog first\n      for (const name of toolsetNames) {\n        if (!catalog[name]) {\n          return {\n            isValid: false,\n            error: `Toolset '${name}' not found in catalog`,\n          };\n        }\n      }\n\n      // Get modules - empty array is valid (toolset may have only direct tools)\n      const modules = this.getModulesForToolSets(toolsetNames, catalog);\n      return { isValid: true, modules };\n    } catch (error) {\n      return {\n        isValid: false,\n        error: `Error resolving modules for ${toolsetNames.join(\", \")}: ${\n          error instanceof Error ? error.message : \"Unknown error\"\n        }`,\n      };\n    }\n  }\n\n  private isDynamicEnabled(\n    source?: Record<string, unknown> | Record<string, string | undefined>\n  ): boolean {\n    if (!source) return false;\n    for (const key of this.keys.dynamic) {\n      const value = (source as any)[key];\n      if (value === true) return true;\n      if (typeof value === \"string\") {\n        const v = value.trim().toLowerCase();\n        if (v === \"true\") return true;\n      }\n    }\n    return false;\n  }\n\n  private getToolsetsString(\n    source?: Record<string, unknown> | Record<string, string | undefined>\n  ): string | undefined {\n    if (!source) return undefined;\n    for (const key of this.keys.toolsets) {\n      const value = (source as any)[key];\n      if (typeof value === \"string\" && value.trim().length > 0)\n        return value as string;\n    }\n    return undefined;\n  }\n}\n","import type {\n  ToolSetCatalog,\n  ToolSetDefinition,\n  McpToolDefinition,\n  ModuleLoader,\n} from \"../types/index.js\";\n\nexport interface ModuleResolverOptions {\n  catalog: ToolSetCatalog;\n  moduleLoaders?: Record<string, ModuleLoader>;\n}\n\nexport class ModuleResolver {\n  private readonly catalog: ToolSetCatalog;\n  private readonly moduleLoaders: Record<string, ModuleLoader>;\n\n  constructor(options: ModuleResolverOptions) {\n    this.catalog = options.catalog;\n    this.moduleLoaders = options.moduleLoaders ?? {};\n  }\n\n  public getAvailableToolsets(): string[] {\n    return Object.keys(this.catalog);\n  }\n\n  public getToolsetDefinition(name: string): ToolSetDefinition | undefined {\n    return this.catalog[name];\n  }\n\n  public validateToolsetName(name: unknown): {\n    isValid: boolean;\n    sanitized?: string;\n    error?: string;\n  } {\n    if (!name || typeof name !== \"string\") {\n      return {\n        isValid: false,\n        error: `Invalid toolset name provided. Must be a non-empty string. Available toolsets: ${this.getAvailableToolsets().join(\n          \", \"\n        )}`,\n      };\n    }\n    const sanitized = name.trim();\n    if (sanitized.length === 0) {\n      return {\n        isValid: false,\n        error: `Empty toolset name provided. Available toolsets: ${this.getAvailableToolsets().join(\n          \", \"\n        )}`,\n      };\n    }\n    if (!this.catalog[sanitized]) {\n      return {\n        isValid: false,\n        error: `Toolset '${sanitized}' not found. Available toolsets: ${this.getAvailableToolsets().join(\n          \", \"\n        )}`,\n      };\n    }\n    return { isValid: true, sanitized };\n  }\n\n  public async resolveToolsForToolsets(\n    toolsets: string[],\n    context?: unknown\n  ): Promise<McpToolDefinition[]> {\n    const collected: McpToolDefinition[] = [];\n    for (const name of toolsets) {\n      const def = this.catalog[name];\n      if (!def) continue;\n      if (Array.isArray(def.tools) && def.tools.length > 0) {\n        collected.push(...def.tools);\n      }\n      if (Array.isArray(def.modules) && def.modules.length > 0) {\n        for (const modKey of def.modules) {\n          const loader = this.moduleLoaders[modKey];\n          if (!loader) continue;\n          try {\n            const loaded = await loader(context);\n            if (Array.isArray(loaded) && loaded.length > 0) {\n              collected.push(...loaded);\n            }\n          } catch (err) {\n            console.warn(\n              `Module loader '${modKey}' failed for toolset '${name}':`,\n              err\n            );\n          }\n        }\n      }\n    }\n    return collected;\n  }\n}\n","import type { ToolingErrorCode } from \"../types/index.js\";\n\nexport class ToolingError extends Error {\n  public readonly code: ToolingErrorCode;\n  public readonly details?: Record<string, unknown>;\n\n  constructor(\n    message: string,\n    code: ToolingErrorCode,\n    details?: Record<string, unknown>,\n    _options?: unknown\n  ) {\n    super(message);\n    this.name = \"ToolingError\";\n    this.code = code;\n    this.details = details;\n  }\n}\n","import type { McpToolDefinition } from \"../types/index.js\";\nimport { ToolingError } from \"../errors/ToolingError.js\";\n\nexport interface ToolRegistryOptions {\n  namespaceWithToolset?: boolean;\n}\n\nexport class ToolRegistry {\n  private readonly options: Required<ToolRegistryOptions>;\n  private readonly names = new Set<string>();\n  private readonly toolsetToNames = new Map<string, Set<string>>();\n\n  constructor(options: ToolRegistryOptions = {}) {\n    this.options = {\n      namespaceWithToolset: options.namespaceWithToolset ?? true,\n    };\n  }\n\n  public getSafeName(toolsetKey: string, toolName: string): string {\n    if (!this.options.namespaceWithToolset) return toolName;\n    if (toolName.startsWith(`${toolsetKey}.`)) return toolName;\n    return `${toolsetKey}.${toolName}`;\n  }\n\n  public has(name: string): boolean {\n    return this.names.has(name);\n  }\n\n  public add(name: string): void {\n    if (this.names.has(name)) {\n      throw new ToolingError(\n        `Tool name collision: '${name}' already registered`,\n        \"E_TOOL_NAME_CONFLICT\"\n      );\n    }\n    this.names.add(name);\n  }\n\n  public addForToolset(toolsetKey: string, name: string): void {\n    this.add(name);\n    const set = this.toolsetToNames.get(toolsetKey) ?? new Set<string>();\n    set.add(name);\n    this.toolsetToNames.set(toolsetKey, set);\n  }\n\n  public mapAndValidate(\n    toolsetKey: string,\n    tools: McpToolDefinition[]\n  ): McpToolDefinition[] {\n    return tools.map((t) => {\n      const safe = this.getSafeName(toolsetKey, t.name);\n      if (this.has(safe)) {\n        throw new ToolingError(\n          `Tool name collision for '${safe}'`,\n          \"E_TOOL_NAME_CONFLICT\"\n        );\n      }\n      return { ...t, name: safe };\n    });\n  }\n\n  public list(): string[] {\n    return Array.from(this.names);\n  }\n\n  public listByToolset(): Record<string, string[]> {\n    const result: Record<string, string[]> = {};\n    for (const [k, v] of this.toolsetToNames.entries()) {\n      result[k] = Array.from(v);\n    }\n    return result;\n  }\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type {\n  ExposurePolicy,\n  McpToolDefinition,\n  ToolSetDefinition,\n  ToolingErrorCode,\n} from \"../types/index.js\";\nimport { ModuleResolver } from \"../mode/ModuleResolver.js\";\nimport { ToolRegistry } from \"./ToolRegistry.js\";\n\nexport interface DynamicToolManagerOptions {\n  server: McpServer;\n  resolver: ModuleResolver;\n  context?: unknown;\n  onToolsListChanged?: () => Promise<void> | void;\n  exposurePolicy?: ExposurePolicy;\n  toolRegistry?: ToolRegistry;\n}\n\nexport class DynamicToolManager {\n  private readonly server: McpServer;\n  private readonly resolver: ModuleResolver;\n  private readonly context?: unknown;\n  private readonly onToolsListChanged?: () => Promise<void> | void;\n  private readonly exposurePolicy?: ExposurePolicy;\n  private readonly toolRegistry: ToolRegistry;\n\n  private readonly activeToolsets = new Set<string>();\n\n  constructor(options: DynamicToolManagerOptions) {\n    this.server = options.server;\n    this.resolver = options.resolver;\n    this.context = options.context;\n    this.onToolsListChanged = options.onToolsListChanged;\n    this.exposurePolicy = options.exposurePolicy;\n    this.toolRegistry =\n      options.toolRegistry ?? new ToolRegistry({ namespaceWithToolset: true });\n  }\n\n  /**\n   * Sends a tool list change notification if configured.\n   * Logs warnings on failure instead of throwing.\n   * @returns Promise that resolves when notification is sent (or skipped)\n   * @private\n   */\n  private async notifyToolsChanged(): Promise<void> {\n    if (!this.onToolsListChanged) return;\n    try {\n      await this.onToolsListChanged();\n    } catch (err) {\n      console.warn(\"Failed to send tool list change notification:\", err);\n    }\n  }\n\n  public getAvailableToolsets(): string[] {\n    return this.resolver.getAvailableToolsets();\n  }\n\n  public getActiveToolsets(): string[] {\n    return Array.from(this.activeToolsets);\n  }\n\n  public getToolsetDefinition(name: string): ToolSetDefinition | undefined {\n    return this.resolver.getToolsetDefinition(name);\n  }\n\n  public isActive(name: string): boolean {\n    return this.activeToolsets.has(name);\n  }\n\n  /**\n   * Enables a single toolset by name.\n   * Validates the toolset, checks exposure policies, resolves tools, and registers them.\n   * @param toolsetName - The name of the toolset to enable\n   * @param skipNotification - If true, skips the tool list change notification (for batch operations)\n   * @returns Result object with success status and message\n   */\n  public async enableToolset(\n    toolsetName: string,\n    skipNotification = false\n  ): Promise<{ success: boolean; message: string }> {\n    const validation = this.resolver.validateToolsetName(toolsetName);\n    if (!validation.isValid || !validation.sanitized) {\n      return {\n        success: false,\n        message: validation.error || \"Unknown validation error\",\n      };\n    }\n    const sanitized = validation.sanitized;\n    if (this.activeToolsets.has(sanitized)) {\n      return {\n        success: false,\n        message: `Toolset '${sanitized}' is already enabled.`,\n      };\n    }\n\n    // Check exposure policies BEFORE resolving tools to fail fast\n    const policyCheck = this.checkExposurePolicy(sanitized);\n    if (!policyCheck.allowed) {\n      return { success: false, message: policyCheck.message };\n    }\n\n    // Track tools registered for this enable operation to allow rollback\n    const registeredTools: string[] = [];\n\n    try {\n      const resolvedTools = await this.resolver.resolveToolsForToolsets(\n        [sanitized],\n        this.context\n      );\n\n      // Register all resolved tools (direct + module-derived)\n      if (resolvedTools && resolvedTools.length > 0) {\n        const mapped = this.toolRegistry.mapAndValidate(\n          sanitized,\n          resolvedTools\n        );\n        for (const tool of mapped) {\n          this.registerSingleTool(tool, sanitized);\n          registeredTools.push(tool.name);\n        }\n      }\n\n      // Track state only after successful registration\n      this.activeToolsets.add(sanitized);\n\n      // Notify list change (unless skipped for batch operations)\n      if (!skipNotification) {\n        await this.notifyToolsChanged();\n      }\n\n      return {\n        success: true,\n        message: `Toolset '${sanitized}' enabled successfully. Registered ${\n          resolvedTools?.length ?? 0\n        } tools.`,\n      };\n    } catch (error) {\n      // Note: We cannot unregister tools from MCP server, but we can track the inconsistency\n      if (registeredTools.length > 0) {\n        console.warn(\n          `Partial failure enabling toolset '${sanitized}'. ` +\n            `${registeredTools.length} tools were registered but toolset activation failed. ` +\n            `Tools remain registered due to MCP limitations: ${registeredTools.join(\", \")}`\n        );\n      }\n      // Don't add to activeToolsets since we failed\n      return {\n        success: false,\n        message: `Failed to enable toolset '${sanitized}': ${\n          error instanceof Error ? error.message : \"Unknown error\"\n        }`,\n      };\n    }\n  }\n\n  /**\n   * Checks if a toolset is allowed by the exposure policy.\n   * @param toolsetName - The sanitized toolset name to check\n   * @returns Object indicating if allowed and reason message if not\n   * @private\n   */\n  private checkExposurePolicy(toolsetName: string): {\n    allowed: boolean;\n    message: string;\n  } {\n    if (\n      this.exposurePolicy?.allowlist &&\n      !this.exposurePolicy.allowlist.includes(toolsetName)\n    ) {\n      return {\n        allowed: false,\n        message: `Toolset '${toolsetName}' is not allowed by policy.`,\n      };\n    }\n    if (\n      this.exposurePolicy?.denylist &&\n      this.exposurePolicy.denylist.includes(toolsetName)\n    ) {\n      return {\n        allowed: false,\n        message: `Toolset '${toolsetName}' is denied by policy.`,\n      };\n    }\n    if (this.exposurePolicy?.maxActiveToolsets !== undefined) {\n      const next = this.activeToolsets.size + 1;\n      if (next > this.exposurePolicy.maxActiveToolsets) {\n        this.exposurePolicy.onLimitExceeded?.(\n          [toolsetName],\n          Array.from(this.activeToolsets)\n        );\n        return {\n          allowed: false,\n          message: `Activation exceeds maxActiveToolsets (${this.exposurePolicy.maxActiveToolsets}).`,\n        };\n      }\n    }\n    return { allowed: true, message: \"\" };\n  }\n\n  /**\n   * Registers a single tool with the MCP server.\n   * @param tool - The tool definition to register\n   * @param toolsetKey - The toolset key for tracking\n   * @private\n   */\n  private registerSingleTool(tool: McpToolDefinition, toolsetKey: string): void {\n    // Only pass annotations if they exist and are not empty\n    const hasAnnotations =\n      tool.annotations && Object.keys(tool.annotations).length > 0;\n\n    if (hasAnnotations && tool.annotations) {\n      this.server.tool(\n        tool.name,\n        tool.description,\n        tool.inputSchema as Parameters<typeof this.server.tool>[2],\n        tool.annotations,\n        async (args: Record<string, unknown>) => {\n          return await tool.handler(args);\n        }\n      );\n    } else {\n      // Legacy 4-parameter call when no annotations\n      this.server.tool(\n        tool.name,\n        tool.description,\n        tool.inputSchema as Parameters<typeof this.server.tool>[2],\n        async (args: Record<string, unknown>) => {\n          return await tool.handler(args);\n        }\n      );\n    }\n    this.toolRegistry.addForToolset(toolsetKey, tool.name);\n  }\n\n  /**\n   * Disables a toolset by name.\n   * Note: Due to MCP limitations, tools remain registered but the toolset is marked inactive.\n   * @param toolsetName - The name of the toolset to disable\n   * @returns Result object with success status and message\n   */\n  public async disableToolset(\n    toolsetName: string\n  ): Promise<{ success: boolean; message: string }> {\n    const validation = this.resolver.validateToolsetName(toolsetName);\n    if (!validation.isValid || !validation.sanitized) {\n      const activeToolsets =\n        Array.from(this.activeToolsets).join(\", \") || \"none\";\n      const base = validation.error || \"Unknown validation error\";\n      return {\n        success: false,\n        message: `${base} Active toolsets: ${activeToolsets}`,\n      };\n    }\n    const sanitized = validation.sanitized;\n    if (!this.activeToolsets.has(sanitized)) {\n      return {\n        success: false,\n        message: `Toolset '${sanitized}' is not currently active. Active toolsets: ${\n          Array.from(this.activeToolsets).join(\", \") || \"none\"\n        }`,\n      };\n    }\n\n    // State-only disable; no unregistration support in MCP\n    this.activeToolsets.delete(sanitized);\n\n    await this.notifyToolsChanged();\n\n    return {\n      success: true,\n      message: `Toolset '${sanitized}' disabled successfully. Individual tools remain registered due to MCP limitations.`,\n    };\n  }\n\n  public getStatus() {\n    return {\n      availableToolsets: this.getAvailableToolsets(),\n      activeToolsets: this.getActiveToolsets(),\n      registeredModules: [],\n      totalToolsets: this.getAvailableToolsets().length,\n      activeCount: this.activeToolsets.size,\n      tools: this.toolRegistry.list(),\n      toolsetToTools: this.toolRegistry.listByToolset(),\n    };\n  }\n\n  /**\n   * Enables multiple toolsets in a batch operation.\n   * Sends a single notification after all toolsets are processed.\n   * @param toolsetNames - Array of toolset names to enable\n   * @returns Result object with overall success status and individual results\n   */\n  public async enableToolsets(toolsetNames: string[]): Promise<{\n    success: boolean;\n    results: Array<{\n      name: string;\n      success: boolean;\n      message: string;\n      code?: ToolingErrorCode;\n    }>;\n    message: string;\n  }> {\n    const results: Array<{\n      name: string;\n      success: boolean;\n      message: string;\n      code?: ToolingErrorCode;\n    }> = [];\n\n    // Enable each toolset, skipping individual notifications\n    for (const name of toolsetNames) {\n      try {\n        const res = await this.enableToolset(name, true);\n        results.push({ name, ...res });\n      } catch (err) {\n        results.push({\n          name,\n          success: false,\n          message: err instanceof Error ? err.message : \"Unknown error\",\n          code: \"E_INTERNAL\",\n        });\n      }\n    }\n\n    const successAll = results.every((r) => r.success);\n    const anySuccess = results.some((r) => r.success);\n    const message = successAll\n      ? \"All toolsets enabled\"\n      : anySuccess\n        ? \"Some toolsets failed to enable\"\n        : \"All toolsets failed to enable\";\n\n    // Send a single notification after batch is complete (if any changes occurred)\n    if (anySuccess) {\n      await this.notifyToolsChanged();\n    }\n\n    return { success: successAll, results, message };\n  }\n\n  /**\n   * Enables all available toolsets in a batch operation.\n   * @returns Result object with overall success status and individual results\n   */\n  public async enableAllToolsets(): Promise<{\n    success: boolean;\n    results: Array<{\n      name: string;\n      success: boolean;\n      message: string;\n      code?: ToolingErrorCode;\n    }>;\n    message: string;\n  }> {\n    const all = this.getAvailableToolsets();\n    return this.enableToolsets(all);\n  }\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { Mode } from \"../types/index.js\";\nimport { z } from \"zod\";\nimport { DynamicToolManager } from \"../core/DynamicToolManager.js\";\n\n/**\n * Registers meta-tools on the MCP server for toolset management.\n *\n * In DYNAMIC mode, all meta-tools are registered:\n * - enable_toolset, disable_toolset: For runtime toolset management\n * - list_toolsets, describe_toolset: For toolset discovery\n * - list_tools: For listing registered tools\n *\n * In STATIC mode, only list_tools is registered since toolsets are fixed at startup.\n *\n * @param server - The MCP server to register tools on\n * @param manager - The DynamicToolManager instance\n * @param options - Configuration options including the mode\n */\nexport function registerMetaTools(\n  server: McpServer,\n  manager: DynamicToolManager,\n  options?: { mode?: Exclude<Mode, \"ALL\"> }\n): void {\n  const mode = options?.mode ?? \"DYNAMIC\";\n\n  // Dynamic-mode only tools: enable/disable toolsets at runtime\n  if (mode === \"DYNAMIC\") {\n    server.tool(\n      \"enable_toolset\",\n      \"Enable a toolset by name\",\n      { name: z.string().describe(\"Toolset name\") },\n      { destructiveHint: true, idempotentHint: true },\n      async (args: { name: string }) => {\n        const result = await manager.enableToolset(args.name);\n        return {\n          content: [{ type: \"text\", text: JSON.stringify(result) }],\n        };\n      }\n    );\n\n    server.tool(\n      \"disable_toolset\",\n      \"Disable a toolset by name (state only)\",\n      { name: z.string().describe(\"Toolset name\") },\n      { destructiveHint: true, idempotentHint: true },\n      async (args: { name: string }) => {\n        const result = await manager.disableToolset(args.name);\n        return {\n          content: [{ type: \"text\", text: JSON.stringify(result) }],\n        };\n      }\n    );\n\n    server.tool(\n      \"list_toolsets\",\n      \"List available toolsets with active status and definitions\",\n      {},\n      { readOnlyHint: true, idempotentHint: true },\n      async () => {\n        const available = manager.getAvailableToolsets();\n        const byToolset = manager.getStatus().toolsetToTools;\n        const items = available.map((key) => {\n          const def = manager.getToolsetDefinition(key);\n          return {\n            key,\n            active: manager.isActive(key),\n            definition: def\n              ? {\n                  name: def.name,\n                  description: def.description,\n                  modules: def.modules ?? [],\n                  decisionCriteria: def.decisionCriteria ?? undefined,\n                }\n              : null,\n            tools: byToolset[key] ?? [],\n          };\n        });\n        return {\n          content: [\n            { type: \"text\", text: JSON.stringify({ toolsets: items }) },\n          ],\n        };\n      }\n    );\n\n    server.tool(\n      \"describe_toolset\",\n      \"Describe a toolset with definition, active status and tools\",\n      { name: z.string().describe(\"Toolset name\") },\n      { readOnlyHint: true, idempotentHint: true },\n      async (args: { name: string }) => {\n        const def = manager.getToolsetDefinition(args.name);\n        const byToolset = manager.getStatus().toolsetToTools;\n        if (!def) {\n          return {\n            content: [\n              {\n                type: \"text\",\n                text: JSON.stringify({ error: `Unknown toolset '${args.name}'` }),\n              },\n            ],\n          };\n        }\n        const payload = {\n          key: args.name,\n          active: manager.isActive(args.name),\n          definition: {\n            name: def.name,\n            description: def.description,\n            modules: def.modules ?? [],\n            decisionCriteria: def.decisionCriteria ?? undefined,\n          },\n          tools: byToolset[args.name] ?? [],\n        };\n        return {\n          content: [{ type: \"text\", text: JSON.stringify(payload) }],\n        };\n      }\n    );\n  }\n\n  // list_tools is available in both modes\n  server.tool(\n    \"list_tools\",\n    \"List currently registered tool names (best effort)\",\n    {},\n    { readOnlyHint: true, idempotentHint: true },\n    async () => {\n      const status = manager.getStatus();\n      const payload = {\n        tools: status.tools,\n        toolsetToTools: status.toolsetToTools,\n      };\n      return {\n        content: [{ type: \"text\", text: JSON.stringify(payload) }],\n      };\n    }\n  );\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { ToolsetValidator } from \"../mode/ToolsetValidator.js\";\nimport { ModuleResolver } from \"../mode/ModuleResolver.js\";\nimport { DynamicToolManager } from \"./DynamicToolManager.js\";\nimport { registerMetaTools } from \"../meta/registerMetaTools.js\";\nimport type {\n  ExposurePolicy,\n  Mode,\n  ModuleLoader,\n  ToolSetCatalog,\n} from \"../types/index.js\";\nimport { ToolRegistry } from \"./ToolRegistry.js\";\n\nexport interface ServerOrchestratorOptions {\n  server: McpServer;\n  catalog: ToolSetCatalog;\n  moduleLoaders?: Record<string, ModuleLoader>;\n  exposurePolicy?: ExposurePolicy;\n  context?: unknown;\n  notifyToolsListChanged?: () => Promise<void> | void;\n  startup?: { mode?: Exclude<Mode, \"ALL\">; toolsets?: string[] | \"ALL\" };\n  registerMetaTools?: boolean;\n}\n\nexport class ServerOrchestrator {\n  private readonly mode: Exclude<Mode, \"ALL\">;\n  private readonly resolver: ModuleResolver;\n  private readonly manager: DynamicToolManager;\n  private readonly toolsetValidator: ToolsetValidator;\n  private readonly initPromise: Promise<void>;\n  private initError: Error | null = null;\n\n  constructor(options: ServerOrchestratorOptions) {\n    this.toolsetValidator = new ToolsetValidator();\n    const startup = options.startup ?? {};\n    const resolved = this.resolveStartupConfig(startup, options.catalog);\n    this.mode = resolved.mode;\n    this.resolver = new ModuleResolver({\n      catalog: options.catalog,\n      moduleLoaders: options.moduleLoaders,\n    });\n    const toolRegistry = new ToolRegistry({\n      namespaceWithToolset:\n        options.exposurePolicy?.namespaceToolsWithSetKey ?? true,\n    });\n    this.manager = new DynamicToolManager({\n      server: options.server,\n      resolver: this.resolver,\n      context: options.context,\n      onToolsListChanged: options.notifyToolsListChanged,\n      exposurePolicy: options.exposurePolicy,\n      toolRegistry,\n    });\n\n    // Register meta-tools only if requested (default true)\n    if (options.registerMetaTools !== false) {\n      registerMetaTools(options.server, this.manager, { mode: this.mode });\n    }\n\n    // Startup behavior - store promise for async initialization\n    const initial = resolved.toolsets;\n    this.initPromise = this.initializeToolsets(initial);\n  }\n\n  /**\n   * Initializes toolsets asynchronously during construction.\n   * Stores any errors for later retrieval via ensureReady().\n   * @param initial - The toolsets to initialize or \"ALL\"\n   * @returns Promise that resolves when initialization is complete\n   * @private\n   */\n  private async initializeToolsets(\n    initial: string[] | \"ALL\" | undefined\n  ): Promise<void> {\n    try {\n      if (initial === \"ALL\") {\n        await this.manager.enableToolsets(this.resolver.getAvailableToolsets());\n      } else if (Array.isArray(initial) && initial.length > 0) {\n        await this.manager.enableToolsets(initial);\n      }\n    } catch (error) {\n      this.initError =\n        error instanceof Error ? error : new Error(String(error));\n      console.error(\"Failed to initialize toolsets:\", this.initError);\n    }\n  }\n\n  /**\n   * Waits for the orchestrator to be fully initialized.\n   * Call this before using the orchestrator to ensure all toolsets are loaded.\n   * @throws {Error} If initialization failed\n   */\n  public async ensureReady(): Promise<void> {\n    await this.initPromise;\n    if (this.initError) {\n      throw this.initError;\n    }\n  }\n\n  /**\n   * Checks if the orchestrator has finished initialization.\n   * Does not throw on error - use ensureReady() for that.\n   * @returns Promise that resolves to true if ready, false if initialization failed\n   */\n  public async isReady(): Promise<boolean> {\n    await this.initPromise;\n    return this.initError === null;\n  }\n\n  private resolveStartupConfig(\n    startup: { mode?: Exclude<Mode, \"ALL\">; toolsets?: string[] | \"ALL\" },\n    catalog: ToolSetCatalog\n  ): { mode: Exclude<Mode, \"ALL\">; toolsets?: string[] | \"ALL\" } {\n    // Explicit mode dominates\n    if (startup.mode) {\n      if (startup.mode === \"DYNAMIC\" && startup.toolsets) {\n        console.warn(\"startup.toolsets provided but ignored in DYNAMIC mode\");\n        return { mode: \"DYNAMIC\" };\n      }\n      if (startup.mode === \"STATIC\") {\n        if (startup.toolsets === \"ALL\")\n          return { mode: \"STATIC\", toolsets: \"ALL\" };\n        const names = Array.isArray(startup.toolsets) ? startup.toolsets : [];\n        const valid: string[] = [];\n        for (const name of names) {\n          const { isValid, sanitized, error } =\n            this.toolsetValidator.validateToolsetName(name, catalog);\n          if (isValid && sanitized) valid.push(sanitized);\n          else if (error) console.warn(error);\n        }\n        if (names.length > 0 && valid.length === 0) {\n          throw new Error(\n            \"STATIC mode requires valid toolsets or 'ALL'; none were valid\"\n          );\n        }\n        return { mode: \"STATIC\", toolsets: valid };\n      }\n      return { mode: startup.mode };\n    }\n\n    // No explicit mode; infer from toolsets\n    if (startup.toolsets === \"ALL\") return { mode: \"STATIC\", toolsets: \"ALL\" };\n    if (Array.isArray(startup.toolsets) && startup.toolsets.length > 0) {\n      const valid: string[] = [];\n      for (const name of startup.toolsets) {\n        const { isValid, sanitized, error } =\n          this.toolsetValidator.validateToolsetName(name, catalog);\n        if (isValid && sanitized) valid.push(sanitized);\n        else if (error) console.warn(error);\n      }\n      if (valid.length === 0) {\n        throw new Error(\n          \"STATIC mode requires valid toolsets or 'ALL'; none were valid\"\n        );\n      }\n      return { mode: \"STATIC\", toolsets: valid };\n    }\n\n    // Default\n    return { mode: \"DYNAMIC\" };\n  }\n\n  public getMode(): Exclude<Mode, \"ALL\"> {\n    return this.mode;\n  }\n\n  public getManager(): DynamicToolManager {\n    return this.manager;\n  }\n}\n","export interface ClientResourceCacheOptions<T> {\n  maxSize?: number;\n  ttlMs?: number; // ms\n  pruneIntervalMs?: number;\n  /**\n   * Optional cleanup callback called when a resource is removed from the cache.\n   * Use this to close connections, clean up sessions, etc.\n   * @param key - The cache key being removed\n   * @param resource - The resource being removed\n   */\n  onEvict?: (key: string, resource: T) => void | Promise<void>;\n}\n\ninterface Entry<T> {\n  resource: T;\n  lastAccessed: number;\n}\n\nexport class ClientResourceCache<T> {\n  private storage = new Map<string, Entry<T>>();\n  private maxSize: number;\n  private ttlMs: number;\n  private onEvict?: (key: string, resource: T) => void | Promise<void>;\n  // Use ReturnType<typeof setInterval> for cross-env typings without NodeJS namespace\n  private pruneInterval?: ReturnType<typeof setInterval>;\n\n  constructor(options: ClientResourceCacheOptions<T> = {}) {\n    this.maxSize = options.maxSize ?? 1000;\n    this.ttlMs = options.ttlMs ?? 1000 * 60 * 60;\n    this.onEvict = options.onEvict;\n    const pruneEvery = options.pruneIntervalMs ?? 1000 * 60 * 10;\n    this.pruneInterval = setInterval(() => this.pruneExpired(), pruneEvery);\n  }\n\n  public getEntryCount(): number {\n    return this.storage.size;\n  }\n\n  public getMaxSize(): number {\n    return this.maxSize;\n  }\n\n  public getTtl(): number {\n    return this.ttlMs;\n  }\n\n  public get(key: string): T | null {\n    const entry = this.storage.get(key);\n    if (!entry) return null;\n    if (Date.now() - entry.lastAccessed > this.ttlMs) {\n      this.delete(key);\n      return null;\n    }\n    entry.lastAccessed = Date.now();\n    this.storage.delete(key);\n    this.storage.set(key, entry);\n    return entry.resource;\n  }\n\n  public set(key: string, resource: T): void {\n    if (this.storage.size >= this.maxSize) {\n      this.evictLeastRecentlyUsed();\n    }\n    const newEntry: Entry<T> = { resource, lastAccessed: Date.now() };\n    this.storage.set(key, newEntry);\n  }\n\n  /**\n   * Removes an entry from the cache.\n   * Calls the onEvict callback if configured.\n   * @param key - The key to remove\n   */\n  public delete(key: string): void {\n    const entry = this.storage.get(key);\n    if (entry) {\n      this.storage.delete(key);\n      this.#callEvictCallback(key, entry.resource);\n    }\n  }\n\n  /**\n   * Stops the background pruning interval and optionally clears all entries.\n   * @param clearEntries - If true, also removes all entries and calls onEvict for each\n   */\n  public stop(clearEntries = false): void {\n    if (this.pruneInterval) {\n      clearInterval(this.pruneInterval);\n      this.pruneInterval = undefined;\n    }\n    if (clearEntries) {\n      this.clear();\n    }\n  }\n\n  /**\n   * Clears all entries from the cache.\n   * Calls onEvict for each entry being removed.\n   */\n  public clear(): void {\n    // Collect all entries first to avoid modification during iteration\n    const entries = Array.from(this.storage.entries());\n    this.storage.clear();\n    for (const [key, entry] of entries) {\n      this.#callEvictCallback(key, entry.resource);\n    }\n  }\n\n  /**\n   * Evicts the least recently used entry from the cache.\n   * @private\n   */\n  private evictLeastRecentlyUsed(): void {\n    const lruKey = this.storage.keys().next().value as string | undefined;\n    if (lruKey) {\n      this.delete(lruKey);\n    }\n  }\n\n  /**\n   * Removes all expired entries from the cache.\n   * @private\n   */\n  private pruneExpired(): void {\n    const now = Date.now();\n    const keysToDelete: string[] = [];\n    for (const [key, entry] of this.storage.entries()) {\n      if (now - entry.lastAccessed > this.ttlMs) {\n        keysToDelete.push(key);\n      }\n    }\n    // Delete after iteration to avoid modification during iteration\n    for (const key of keysToDelete) {\n      this.delete(key);\n    }\n  }\n\n  /**\n   * Safely calls the evict callback, catching and logging any errors.\n   * @param key - The key being evicted\n   * @param resource - The resource being evicted\n   * @private\n   */\n  #callEvictCallback(key: string, resource: T): void {\n    if (!this.onEvict) return;\n    try {\n      const result = this.onEvict(key, resource);\n      // Handle async callbacks but don't await\n      if (result instanceof Promise) {\n        result.catch((err) => {\n          console.warn(`Error in cache eviction callback for key '${key}':`, err);\n        });\n      }\n    } catch (err) {\n      console.warn(`Error in cache eviction callback for key '${key}':`, err);\n    }\n  }\n}\n","import type { FastifyInstance, FastifyRequest, FastifyReply } from \"fastify\";\nimport { z } from \"zod\";\nimport { randomUUID } from \"node:crypto\";\nimport type {\n  CustomEndpointDefinition,\n  CustomEndpointRequest,\n  EndpointErrorResponse,\n} from \"./customEndpoints.js\";\n\n/**\n * Options for registering custom endpoints\n */\nexport interface RegisterCustomEndpointsOptions {\n  /**\n   * Optional function to extract additional context for each request.\n   * Used by permission-aware transport to inject permission data.\n   */\n  contextExtractor?: (\n    req: FastifyRequest\n  ) => Promise<Record<string, any>> | Record<string, any>;\n}\n\n/**\n * Registers custom endpoints on a Fastify instance.\n * Handles Zod validation, error responses, and type-safe request mapping.\n *\n * @param app - Fastify instance to register endpoints on\n * @param basePath - Base path for all endpoints (e.g., \"/\" or \"/api\")\n * @param endpoints - Array of custom endpoint definitions\n * @param options - Optional configuration for endpoint registration\n *\n * @example\n * ```typescript\n * registerCustomEndpoints(app, \"/api\", [\n *   defineEndpoint({\n *     method: \"GET\",\n *     path: \"/users\",\n *     querySchema: z.object({ limit: z.coerce.number() }),\n *     handler: async (req) => ({ users: [] }),\n *   }),\n * ]);\n * ```\n */\nexport function registerCustomEndpoints(\n  app: FastifyInstance,\n  basePath: string,\n  endpoints: CustomEndpointDefinition[],\n  options?: RegisterCustomEndpointsOptions\n): void {\n  // Built-in MCP paths that should not be overridden\n  const reservedPaths = [\"/mcp\", \"/healthz\", \"/tools\", \"/.well-known/mcp-config\"];\n\n  for (const endpoint of endpoints) {\n    const fullPath = `${basePath}${endpoint.path}`;\n\n    // Check for path conflicts with built-in endpoints\n    const isReserved = reservedPaths.some((reserved) =>\n      fullPath.startsWith(`${basePath}${reserved}`)\n    );\n\n    if (isReserved) {\n      console.warn(\n        `Custom endpoint ${endpoint.method} ${endpoint.path} conflicts with built-in MCP endpoint. Skipping registration.`\n      );\n      continue;\n    }\n\n    // Convert method to lowercase for Fastify\n    const method = endpoint.method.toLowerCase() as\n      | \"get\"\n      | \"post\"\n      | \"put\"\n      | \"delete\"\n      | \"patch\";\n\n    // Register the endpoint with Fastify\n    app[method](fullPath, async (req: FastifyRequest, reply: FastifyReply) => {\n      try {\n        // Extract client ID from header or generate anonymous ID\n        const clientIdHeader = (req.headers[\"mcp-client-id\"] as string)?.trim();\n        const clientId =\n          clientIdHeader && clientIdHeader.length > 0\n            ? clientIdHeader\n            : `anon-${randomUUID()}`;\n\n        // Validate request body if schema provided\n        let body: any = undefined;\n        if (endpoint.bodySchema) {\n          const bodyResult = endpoint.bodySchema.safeParse(req.body);\n          if (!bodyResult.success) {\n            return createValidationError(reply, \"body\", bodyResult.error);\n          }\n          body = bodyResult.data;\n        }\n\n        // Validate query parameters if schema provided\n        let query: any = {};\n        if (endpoint.querySchema) {\n          const queryResult = endpoint.querySchema.safeParse(req.query);\n          if (!queryResult.success) {\n            return createValidationError(reply, \"query\", queryResult.error);\n          }\n          query = queryResult.data;\n        }\n\n        // Validate path parameters if schema provided\n        let params: any = {};\n        if (endpoint.paramsSchema) {\n          const paramsResult = endpoint.paramsSchema.safeParse(req.params);\n          if (!paramsResult.success) {\n            return createValidationError(reply, \"params\", paramsResult.error);\n          }\n          params = paramsResult.data;\n        }\n\n        // Build request object with validated data\n        const customRequest: CustomEndpointRequest = {\n          body,\n          query,\n          params,\n          headers: req.headers as Record<string, string | string[] | undefined>,\n          clientId,\n        };\n\n        // Merge additional context if provided (e.g., permissions from contextExtractor)\n        if (options?.contextExtractor) {\n          const additionalContext = await options.contextExtractor(req);\n          Object.assign(customRequest, additionalContext);\n        }\n\n        // Call the user-defined handler with validated and typed data\n        const result = await endpoint.handler(customRequest as any);\n\n        // Validate response if schema provided\n        if (endpoint.responseSchema) {\n          const responseResult = endpoint.responseSchema.safeParse(result);\n          if (!responseResult.success) {\n            // Log the validation error for debugging\n            console.error(\n              `Response validation failed for ${endpoint.method} ${endpoint.path}:`,\n              responseResult.error\n            );\n\n            // Return generic error to prevent information leakage\n            reply.code(500);\n            return {\n              error: {\n                code: \"RESPONSE_VALIDATION_ERROR\",\n                message: \"Internal server error: invalid response format\",\n              },\n            } as EndpointErrorResponse;\n          }\n          // Return validated response data\n          return responseResult.data;\n        }\n\n        // No response validation - return result as-is\n        return result;\n      } catch (error) {\n        // Handle any errors thrown by the handler\n        console.error(\n          `Error in custom endpoint ${endpoint.method} ${endpoint.path}:`,\n          error\n        );\n\n        reply.code(500);\n        return {\n          error: {\n            code: \"INTERNAL_ERROR\",\n            message:\n              error instanceof Error ? error.message : \"Internal server error\",\n          },\n        } as EndpointErrorResponse;\n      }\n    });\n  }\n}\n\n/**\n * Creates a standardized validation error response.\n * Returns 400 status code with detailed Zod validation errors.\n *\n * @param reply - Fastify reply object\n * @param field - The field that failed validation (body, query, or params)\n * @param error - Zod validation error\n * @returns Formatted error response\n * @private\n */\nfunction createValidationError(\n  reply: FastifyReply,\n  field: string,\n  error: z.ZodError\n): EndpointErrorResponse {\n  reply.code(400);\n  return {\n    error: {\n      code: \"VALIDATION_ERROR\",\n      message: `Validation failed for ${field}`,\n      details: error.errors,\n    },\n  };\n}\n","import Fastify, {\n  type FastifyInstance,\n  type FastifyReply,\n  type FastifyRequest,\n} from \"fastify\";\nimport cors from \"@fastify/cors\";\nimport { randomUUID } from \"node:crypto\";\nimport type { DynamicToolManager } from \"../core/DynamicToolManager.js\";\nimport type { ServerOrchestrator } from \"../core/ServerOrchestrator.js\";\nimport { ClientResourceCache } from \"../session/ClientResourceCache.js\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport { isInitializeRequest } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { CustomEndpointDefinition } from \"./customEndpoints.js\";\nimport { registerCustomEndpoints } from \"./endpointRegistration.js\";\n\nexport interface FastifyTransportOptions {\n  host?: string;\n  port?: number;\n  basePath?: string; // e.g. \"/\" or \"/api\"\n  cors?: boolean;\n  logger?: boolean;\n  // Optional DI: provide a Fastify instance (e.g., for tests). If provided, start() will not listen.\n  app?: FastifyInstance;\n  /**\n   * Optional custom HTTP endpoints to register alongside MCP protocol endpoints.\n   * Allows adding REST-like endpoints with Zod validation and type inference.\n   */\n  customEndpoints?: CustomEndpointDefinition[];\n}\n\nexport class FastifyTransport {\n  private readonly options: {\n    host: string;\n    port: number;\n    basePath: string;\n    cors: boolean;\n    logger: boolean;\n    app?: FastifyInstance;\n    customEndpoints?: CustomEndpointDefinition[];\n  };\n  private readonly defaultManager: DynamicToolManager;\n  private readonly createBundle: () => {\n    server: McpServer;\n    orchestrator: ServerOrchestrator;\n  };\n  private app: FastifyInstance | null = null;\n  private readonly configSchema?: object;\n\n  // Per-client server bundles and per-client session transports\n  private readonly clientCache = new ClientResourceCache<{\n    server: McpServer;\n    orchestrator: ServerOrchestrator;\n    sessions: Map<string, StreamableHTTPServerTransport>;\n  }>({\n    onEvict: (_key, bundle) => {\n      // Clean up all sessions when a client bundle is evicted\n      this.cleanupBundle(bundle);\n    },\n  });\n\n  constructor(\n    defaultManager: DynamicToolManager,\n    createBundle: () => { server: McpServer; orchestrator: ServerOrchestrator },\n    options: FastifyTransportOptions = {},\n    configSchema?: object\n  ) {\n    this.defaultManager = defaultManager;\n    this.createBundle = createBundle;\n    this.options = {\n      host: options.host ?? \"0.0.0.0\",\n      port: options.port ?? 3000,\n      basePath: options.basePath ?? \"/\",\n      cors: options.cors ?? true,\n      logger: options.logger ?? false,\n      app: options.app,\n      customEndpoints: options.customEndpoints,\n    };\n    this.configSchema = configSchema;\n  }\n\n  public async start(): Promise<void> {\n    if (this.app) return;\n    const app = this.options.app ?? Fastify({ logger: this.options.logger });\n    if (this.options.cors) {\n      await app.register(cors, { origin: true });\n    }\n\n    const base = this.options.basePath.endsWith(\"/\")\n      ? this.options.basePath.slice(0, -1)\n      : this.options.basePath;\n\n    app.get(`${base}/healthz`, async () => ({ ok: true }));\n\n    app.get(`${base}/tools`, async () => this.defaultManager.getStatus());\n\n    // Config discovery (placeholder schema)\n    app.get(`${base}/.well-known/mcp-config`, async (_req, reply) => {\n      reply.header(\"Content-Type\", \"application/schema+json; charset=utf-8\");\n      const baseSchema = this.configSchema ?? {\n        $schema: \"https://json-schema.org/draft/2020-12/schema\",\n        title: \"MCP Session Configuration\",\n        description: \"Schema for the /mcp endpoint configuration\",\n        type: \"object\",\n        properties: {},\n        required: [],\n        \"x-mcp-version\": \"1.0\",\n        \"x-query-style\": \"dot+bracket\",\n      };\n      return baseSchema;\n    });\n\n    // POST /mcp - JSON-RPC\n    app.post(\n      `${base}/mcp`,\n      async (req: FastifyRequest, reply: FastifyReply) => {\n        const clientIdHeader = (\n          req.headers[\"mcp-client-id\"] as string | undefined\n        )?.trim();\n        const clientId =\n          clientIdHeader && clientIdHeader.length > 0\n            ? clientIdHeader\n            : `anon-${randomUUID()}`;\n\n        // When anon id, avoid caching (one-off)\n        const useCache = !clientId.startsWith(\"anon-\");\n\n        let bundle = useCache ? this.clientCache.get(clientId) : null;\n        if (!bundle) {\n          const created = this.createBundle();\n          const providedSessions = (created as any).sessions;\n          bundle = {\n            server: created.server,\n            orchestrator: created.orchestrator,\n            sessions:\n              providedSessions instanceof Map ? providedSessions : new Map(),\n          };\n          if (useCache) this.clientCache.set(clientId, bundle);\n        }\n\n        const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n\n        let transport: StreamableHTTPServerTransport | undefined;\n        if (sessionId && bundle.sessions.get(sessionId)) {\n          transport = bundle.sessions.get(sessionId)!;\n        } else if (!sessionId && isInitializeRequest((req as any).body)) {\n          const newSessionId = randomUUID();\n          transport = new StreamableHTTPServerTransport({\n            sessionIdGenerator: () => newSessionId,\n            onsessioninitialized: (sid: string) => {\n              bundle!.sessions.set(sid, transport!);\n            },\n          });\n          try {\n            await bundle.server.connect(transport);\n          } catch (error) {\n            reply.code(500);\n            return {\n              jsonrpc: \"2.0\",\n              error: { code: -32603, message: \"Error initializing server.\" },\n              id: null,\n            };\n          }\n          transport.onclose = () => {\n            if (transport?.sessionId)\n              bundle!.sessions.delete(transport.sessionId);\n          };\n        } else {\n          reply.code(400);\n          return {\n            jsonrpc: \"2.0\",\n            error: { code: -32000, message: \"Session not found or expired\" },\n            id: null,\n          };\n        }\n\n        // Delegate handling to SDK transport using raw Node req/res\n        await transport.handleRequest(\n          (req as any).raw,\n          (reply as any).raw,\n          (req as any).body\n        );\n        // Fastify will consider the response already sent by transport\n        return reply;\n      }\n    );\n\n    // GET /mcp - SSE notifications\n    app.get(`${base}/mcp`, async (req: FastifyRequest, reply: FastifyReply) => {\n      const clientIdHeader = (\n        req.headers[\"mcp-client-id\"] as string | undefined\n      )?.trim();\n      const clientId =\n        clientIdHeader && clientIdHeader.length > 0 ? clientIdHeader : \"\";\n      if (!clientId) {\n        reply.code(400);\n        return \"Missing mcp-client-id\";\n      }\n      const bundle = this.clientCache.get(clientId);\n      if (!bundle) {\n        reply.code(400);\n        return \"Invalid or expired client\";\n      }\n      const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n      if (!sessionId) {\n        reply.code(400);\n        return \"Missing mcp-session-id\";\n      }\n      const transport = bundle.sessions.get(sessionId);\n      if (!transport) {\n        reply.code(400);\n        return \"Invalid or expired session ID\";\n      }\n      await transport.handleRequest((req as any).raw, (reply as any).raw);\n      return reply;\n    });\n\n    // DELETE /mcp - terminate session\n    app.delete(\n      `${base}/mcp`,\n      async (req: FastifyRequest, reply: FastifyReply) => {\n        const clientIdHeader = (\n          req.headers[\"mcp-client-id\"] as string | undefined\n        )?.trim();\n        const clientId =\n          clientIdHeader && clientIdHeader.length > 0 ? clientIdHeader : \"\";\n        const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n        if (!clientId || !sessionId) {\n          reply.code(400);\n          return {\n            jsonrpc: \"2.0\",\n            error: {\n              code: -32600,\n              message: \"Missing mcp-client-id or mcp-session-id header\",\n            },\n            id: null,\n          };\n        }\n        const bundle = this.clientCache.get(clientId);\n        const transport = bundle?.sessions.get(sessionId);\n        if (!bundle || !transport) {\n          reply.code(404);\n          return {\n            jsonrpc: \"2.0\",\n            error: { code: -32000, message: \"Session not found or expired\" },\n            id: null,\n          };\n        }\n        try {\n          // Best-effort close and evict\n          if (typeof (transport as any).close === \"function\") {\n            try {\n              await (transport as any).close();\n            } catch {}\n          }\n        } finally {\n          if (transport?.sessionId) bundle.sessions.delete(transport.sessionId);\n          else bundle.sessions.delete(sessionId);\n        }\n        reply.code(204).send();\n        return reply;\n      }\n    );\n\n    // Register custom endpoints if provided\n    // IMPORTANT: Only register if customEndpoints is provided AND has items\n    if (this.options.customEndpoints && this.options.customEndpoints.length > 0) {\n      registerCustomEndpoints(app, base, this.options.customEndpoints);\n    }\n\n    // Only listen if we created the app\n    if (!this.options.app) {\n      await app.listen({ host: this.options.host, port: this.options.port });\n    }\n    this.app = app;\n  }\n\n  /**\n   * Stops the Fastify server and cleans up all resources.\n   * Closes all client sessions and clears the cache.\n   */\n  public async stop(): Promise<void> {\n    if (!this.app) return;\n\n    // Stop the cache pruning interval and clear all entries (triggers cleanup)\n    this.clientCache.stop(true);\n\n    if (!this.options.app) {\n      await this.app.close();\n    }\n    this.app = null;\n  }\n\n  /**\n   * Cleans up resources associated with a client bundle.\n   * Closes all sessions within the bundle.\n   * @param bundle - The client bundle to clean up\n   * @private\n   */\n  private cleanupBundle(bundle: {\n    server: McpServer;\n    orchestrator: ServerOrchestrator;\n    sessions: Map<string, StreamableHTTPServerTransport>;\n  }): void {\n    for (const [sessionId, transport] of bundle.sessions.entries()) {\n      try {\n        if (typeof (transport as any).close === \"function\") {\n          (transport as any).close().catch((err: unknown) => {\n            console.warn(`Error closing session ${sessionId}:`, err);\n          });\n        }\n      } catch (err) {\n        console.warn(`Error closing session ${sessionId}:`, err);\n      }\n    }\n    bundle.sessions.clear();\n  }\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type {\n  ExposurePolicy,\n  Mode,\n  ModuleLoader,\n  ToolSetCatalog,\n} from \"../types/index.js\";\nimport { ServerOrchestrator } from \"../core/ServerOrchestrator.js\";\nimport {\n  FastifyTransport,\n  type FastifyTransportOptions,\n} from \"../http/FastifyTransport.js\";\nimport { z } from \"zod\";\n\nexport interface CreateMcpServerOptions {\n  catalog: ToolSetCatalog;\n  moduleLoaders?: Record<string, ModuleLoader>;\n  exposurePolicy?: ExposurePolicy;\n  context?: unknown;\n  startup?: { mode?: Exclude<Mode, \"ALL\">; toolsets?: string[] | \"ALL\" };\n  registerMetaTools?: boolean;\n  http?: FastifyTransportOptions;\n  /**\n   * Factory to create an MCP server instance. Required.\n   * In DYNAMIC mode, a new instance is created per client bundle.\n   * In STATIC mode, a single instance is created and reused across bundles.\n   */\n  createServer: () => McpServer;\n  configSchema?: object;\n}\n\n/**\n * Zod schema for validating startup configuration.\n * Uses strict mode to reject unknown properties like 'initialToolsets'.\n */\nconst startupConfigSchema = z\n  .object({\n    mode: z.enum([\"DYNAMIC\", \"STATIC\"]).optional(),\n    toolsets: z.union([z.array(z.string()), z.literal(\"ALL\")]).optional(),\n  })\n  .strict();\n\nexport async function createMcpServer(options: CreateMcpServerOptions) {\n  // Validate startup configuration if provided\n  if (options.startup) {\n    try {\n      startupConfigSchema.parse(options.startup);\n    } catch (error) {\n      if (error instanceof z.ZodError) {\n        const formatted = error.format();\n        throw new Error(\n          `Invalid startup configuration:\\n${JSON.stringify(formatted, null, 2)}\\n\\n` +\n            `Hint: Common mistake - use \"toolsets\" not \"initialToolsets\"`\n        );\n      }\n      throw error;\n    }\n  }\n\n  const mode: Exclude<Mode, \"ALL\"> = options.startup?.mode ?? \"DYNAMIC\";\n  if (typeof options.createServer !== \"function\") {\n    throw new Error(\"createMcpServer: `createServer` (factory) is required\");\n  }\n  const baseServer: McpServer = options.createServer();\n\n  // Typed, guarded notifier\n  type NotifierA = {\n    server: { notification: (msg: { method: string }) => Promise<void> | void };\n  };\n  type NotifierB = { notifyToolsListChanged: () => Promise<void> | void };\n  const hasNotifierA = (s: unknown): s is NotifierA =>\n    typeof (s as NotifierA)?.server?.notification === \"function\";\n  const hasNotifierB = (s: unknown): s is NotifierB =>\n    typeof (s as NotifierB)?.notifyToolsListChanged === \"function\";\n\n  /**\n   * Sends a tools list changed notification to the client.\n   * Logs warnings on failure instead of throwing.\n   * Suppresses \"Not connected\" errors as they're expected when no clients are connected.\n   * @param target - The MCP server instance\n   */\n  const notifyToolsChanged = async (target: unknown) => {\n    try {\n      if (hasNotifierA(target)) {\n        await target.server.notification({\n          method: \"notifications/tools/list_changed\",\n        });\n        return;\n      }\n      if (hasNotifierB(target)) {\n        await target.notifyToolsListChanged();\n      }\n    } catch (err) {\n      // Suppress \"Not connected\" errors - expected when no clients are connected\n      const errorMessage = err instanceof Error ? err.message : String(err);\n      if (errorMessage === \"Not connected\") {\n        return; // Silently ignore - no clients to notify\n      }\n      // Log other errors as they indicate actual problems\n      console.warn(\"Failed to send tools list changed notification:\", err);\n    }\n  };\n\n  const orchestrator = new ServerOrchestrator({\n    server: baseServer,\n    catalog: options.catalog,\n    moduleLoaders: options.moduleLoaders,\n    exposurePolicy: options.exposurePolicy,\n    context: options.context,\n    notifyToolsListChanged: async () => notifyToolsChanged(baseServer),\n    startup: options.startup,\n    registerMetaTools:\n      options.registerMetaTools !== undefined\n        ? options.registerMetaTools\n        : mode === \"DYNAMIC\",\n  });\n\n  // In STATIC mode, wait for initialization to complete before starting\n  if (mode === \"STATIC\") {\n    await orchestrator.ensureReady();\n  }\n\n  const transport = new FastifyTransport(\n    orchestrator.getManager(),\n    () => {\n      // Create a server + orchestrator bundle\n      // for a new client when needed\n      if (mode === \"STATIC\") {\n        // Reuse the base server and orchestrator to avoid duplicate registrations\n        return { server: baseServer, orchestrator };\n      }\n      const createdServer: McpServer = options.createServer();\n      const createdOrchestrator = new ServerOrchestrator({\n        server: createdServer,\n        catalog: options.catalog,\n        moduleLoaders: options.moduleLoaders,\n        exposurePolicy: options.exposurePolicy,\n        context: options.context,\n        notifyToolsListChanged: async () => notifyToolsChanged(createdServer),\n        startup: options.startup,\n        registerMetaTools:\n          options.registerMetaTools !== undefined\n            ? options.registerMetaTools\n            : mode === \"DYNAMIC\",\n      });\n      return { server: createdServer, orchestrator: createdOrchestrator };\n    },\n    options.http,\n    options.configSchema\n  );\n\n  return {\n    server: baseServer,\n    start: async () => {\n      await transport.start();\n    },\n    close: async () => {\n      await transport.stop();\n    },\n  };\n}\n","import type { PermissionConfig } from \"../types/index.js\";\n\n/**\n * Validates a permission configuration object to ensure it meets all requirements.\n * Throws descriptive errors for any validation failures.\n * @param config - The permission configuration to validate\n * @throws {Error} If the configuration is invalid or missing required fields\n */\nexport function validatePermissionConfig(config: PermissionConfig): void {\n  validateConfigExists(config);\n  validateSourceField(config);\n  validateConfigBasedPermissions(config);\n  validateTypes(config);\n}\n\n/**\n * Validates that the configuration object exists.\n * @param config - The permission configuration to validate\n * @throws {Error} If config is null, undefined, or not an object\n * @private\n */\nfunction validateConfigExists(config: PermissionConfig): void {\n  if (!config || typeof config !== \"object\") {\n    throw new Error(\n      \"Permission configuration is required for createPermissionBasedMcpServer\"\n    );\n  }\n}\n\n/**\n * Validates that the source field is present and has a valid value.\n * @param config - The permission configuration to validate\n * @throws {Error} If source is missing or not 'headers' or 'config'\n * @private\n */\nfunction validateSourceField(config: PermissionConfig): void {\n  if (!config.source) {\n    throw new Error('Permission source must be either \"headers\" or \"config\"');\n  }\n\n  if (config.source !== \"headers\" && config.source !== \"config\") {\n    throw new Error(\n      `Invalid permission source: \"${config.source}\". Must be either \"headers\" or \"config\"`\n    );\n  }\n}\n\n/**\n * Validates config-based permission requirements.\n * When source is 'config', at least one of staticMap or resolver must be provided.\n * @param config - The permission configuration to validate\n * @throws {Error} If config source is used but neither staticMap nor resolver is provided\n * @private\n */\nfunction validateConfigBasedPermissions(config: PermissionConfig): void {\n  if (config.source === \"config\") {\n    if (!config.staticMap && !config.resolver) {\n      throw new Error(\n        \"Config-based permissions require at least one of: staticMap or resolver function\"\n      );\n    }\n  }\n}\n\n/**\n * Validates the types of configuration fields.\n * Ensures staticMap is an object and resolver is a function when provided.\n * @param config - The permission configuration to validate\n * @throws {Error} If staticMap or resolver have incorrect types\n * @private\n */\nfunction validateTypes(config: PermissionConfig): void {\n  if (config.staticMap !== undefined) {\n    if (typeof config.staticMap !== \"object\" || config.staticMap === null) {\n      throw new Error(\n        \"staticMap must be an object mapping client IDs to toolset arrays\"\n      );\n    }\n\n    // Validate that staticMap values are arrays\n    validateStaticMapValues(config.staticMap);\n  }\n\n  if (config.resolver !== undefined) {\n    if (typeof config.resolver !== \"function\") {\n      throw new Error(\n        \"resolver must be a synchronous function: (clientId: string) => string[]\"\n      );\n    }\n  }\n\n  if (config.defaultPermissions !== undefined) {\n    if (!Array.isArray(config.defaultPermissions)) {\n      throw new Error(\"defaultPermissions must be an array of toolset names\");\n    }\n  }\n\n  if (config.headerName !== undefined) {\n    if (typeof config.headerName !== \"string\" || config.headerName.length === 0) {\n      throw new Error(\"headerName must be a non-empty string\");\n    }\n  }\n}\n\n/**\n * Validates that all values in the staticMap are arrays.\n * @param staticMap - The static map to validate\n * @throws {Error} If any value in the staticMap is not an array\n * @private\n */\nfunction validateStaticMapValues(staticMap: Record<string, string[]>): void {\n  for (const [clientId, permissions] of Object.entries(staticMap)) {\n    if (!Array.isArray(permissions)) {\n      throw new Error(\n        `staticMap value for client \"${clientId}\" must be an array of toolset names`\n      );\n    }\n  }\n}\n","import type { PermissionConfig } from \"../types/index.js\";\n\n/**\n * Resolves and caches client permissions based on configured permission sources.\n * Supports both header-based and config-based permission resolution with caching\n * for performance optimization.\n */\nexport class PermissionResolver {\n  private cache = new Map<string, string[]>();\n  private readonly normalizedHeaderName: string;\n\n  /**\n   * Creates a new PermissionResolver instance.\n   * @param config - The permission configuration defining how permissions are resolved\n   */\n  constructor(private config: PermissionConfig) {\n    // Pre-normalize header name to lowercase for case-insensitive matching\n    this.normalizedHeaderName = (\n      config.headerName || \"mcp-toolset-permissions\"\n    ).toLowerCase();\n  }\n\n  /**\n   * Resolves permissions for a client based on the configured source.\n   * Results are cached to improve performance for subsequent requests from the same client.\n   * Handles all errors gracefully by returning empty permissions on failure.\n   * \n   * Note on caching: For header-based permissions, permissions are cached by clientId.\n   * This means subsequent requests from the same client will use cached permissions,\n   * even if headers change. Use invalidateCache(clientId) to force re-resolution.\n   * \n   * @param clientId - The unique identifier for the client\n   * @param headers - Optional request headers (required for header-based permissions)\n   * @returns Array of toolset names the client is allowed to access\n   */\n  resolvePermissions(\n    clientId: string,\n    headers?: Record<string, string>\n  ): string[] {\n    // Check cache first for performance\n    if (this.cache.has(clientId)) {\n      return this.cache.get(clientId)!;\n    }\n\n    let permissions: string[];\n\n    try {\n      if (this.config.source === \"headers\") {\n        permissions = this.#parseHeaderPermissions(headers);\n      } else {\n        permissions = this.#resolveConfigPermissions(clientId);\n      }\n\n      // Validate that permissions is an array\n      if (!Array.isArray(permissions)) {\n        console.warn(\n          `Permission resolution returned non-array for client ${clientId}, using empty permissions`\n        );\n        permissions = [];\n      }\n\n      // Filter out invalid toolset names (empty strings, non-strings)\n      permissions = permissions.filter(\n        (name) => typeof name === \"string\" && name.trim().length > 0\n      );\n    } catch (error) {\n      // Catch any unexpected errors and apply most restrictive permissions\n      console.error(\n        `Unexpected error resolving permissions for client ${clientId}:`,\n        error\n      );\n      permissions = [];\n    }\n\n    // Cache the resolved permissions\n    this.cache.set(clientId, permissions);\n    return permissions;\n  }\n\n  /**\n   * Invalidates cached permissions for a specific client.\n   * Call this when you know a client's permissions have changed.\n   * @param clientId - The client ID to invalidate\n   */\n  invalidateCache(clientId: string): void {\n    this.cache.delete(clientId);\n  }\n\n  /**\n   * Parses permissions from request headers.\n   * Extracts comma-separated toolset names from the configured header.\n   * Handles malformed headers gracefully by returning empty permissions.\n   * Uses case-insensitive header lookup per RFC 7230.\n   * @param headers - Request headers containing permission data\n   * @returns Array of toolset names from headers, or empty array if header is missing/malformed\n   * @private\n   */\n  #parseHeaderPermissions(headers?: Record<string, string>): string[] {\n    if (!headers) {\n      return [];\n    }\n\n    // Find header value using case-insensitive lookup\n    const headerValue = this.#findHeaderCaseInsensitive(\n      headers,\n      this.normalizedHeaderName\n    );\n\n    if (!headerValue) {\n      return [];\n    }\n\n    try {\n      // Parse comma-separated list, trim whitespace, and filter empty strings\n      return headerValue\n        .split(\",\")\n        .map((s) => s.trim())\n        .filter((s) => s.length > 0);\n    } catch (error) {\n      // Handle malformed headers gracefully\n      console.warn(\n        `Failed to parse permission header '${this.normalizedHeaderName}':`,\n        error\n      );\n      return [];\n    }\n  }\n\n  /**\n   * Finds a header value using case-insensitive key matching.\n   * HTTP headers are case-insensitive per RFC 7230.\n   * @param headers - The headers object to search\n   * @param normalizedKey - The lowercase key to search for\n   * @returns The header value if found, undefined otherwise\n   * @private\n   */\n  #findHeaderCaseInsensitive(\n    headers: Record<string, string>,\n    normalizedKey: string\n  ): string | undefined {\n    // Fast path: check if key exists as-is (common case with Fastify's lowercased headers)\n    if (headers[normalizedKey] !== undefined) {\n      return headers[normalizedKey];\n    }\n    // Slow path: iterate and compare lowercase\n    for (const [key, value] of Object.entries(headers)) {\n      if (key.toLowerCase() === normalizedKey) {\n        return value;\n      }\n    }\n    return undefined;\n  }\n\n  /**\n   * Resolves permissions from server-side configuration.\n   * Tries resolver function first (if provided), then falls back to static map,\n   * and finally to default permissions. Handles errors gracefully.\n   * @param clientId - The unique identifier for the client\n   * @returns Array of toolset names from configuration\n   * @private\n   */\n  #resolveConfigPermissions(clientId: string): string[] {\n    // Try resolver function first (if provided)\n    if (this.config.resolver) {\n      const resolverResult = this.#tryResolverFunction(clientId);\n      if (resolverResult !== null) {\n        return resolverResult;\n      }\n      // Fall through to static map or default if resolver fails\n    }\n\n    // Fall back to static map (if provided)\n    if (this.config.staticMap) {\n      const staticResult = this.#lookupStaticMap(clientId);\n      if (staticResult !== null) {\n        return staticResult;\n      }\n    }\n\n    // Final fallback to default permissions\n    return this.config.defaultPermissions || [];\n  }\n\n  /**\n   * Attempts to resolve permissions using the configured resolver function.\n   * Handles errors gracefully and returns null on failure to allow fallback.\n   * @param clientId - The unique identifier for the client\n   * @returns Array of toolset names if successful, null if resolver fails or returns invalid data\n   * @private\n   */\n  #tryResolverFunction(clientId: string): string[] | null {\n    try {\n      const result = this.config.resolver!(clientId);\n      if (Array.isArray(result)) {\n        return result;\n      }\n      console.warn(\n        `Permission resolver returned non-array for client ${clientId}, using fallback`\n      );\n      return null;\n    } catch (error) {\n      // Log message only, not full stack trace (this is expected fallback behavior)\n      const message = error instanceof Error ? error.message : String(error);\n      console.warn(\n        `Permission resolver declined client ${clientId} (${message}), trying fallback`\n      );\n      return null;\n    }\n  }\n\n  /**\n   * Looks up permissions in the static map configuration.\n   * Returns null if client is not found to allow fallback to defaults.\n   * @param clientId - The unique identifier for the client\n   * @returns Array of toolset names if found, null if client not in map\n   * @private\n   */\n  #lookupStaticMap(clientId: string): string[] | null {\n    const permissions = this.config.staticMap![clientId];\n    if (permissions !== undefined) {\n      return Array.isArray(permissions) ? permissions : [];\n    }\n    return null;\n  }\n\n  /**\n   * Clears the permission cache.\n   * Useful for cleanup during server shutdown or when permissions need to be refreshed.\n   */\n  clearCache(): void {\n    this.cache.clear();\n  }\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { ServerOrchestrator } from \"../core/ServerOrchestrator.js\";\nimport type { PermissionResolver } from \"./PermissionResolver.js\";\n\n/**\n * Context information extracted from a client request.\n * Used to identify the client and resolve their permissions.\n */\nexport interface ClientRequestContext {\n  /**\n   * Unique identifier for the client making the request.\n   * May be provided via mcp-client-id header or generated as anonymous ID.\n   */\n  clientId: string;\n\n  /**\n   * Request headers that may contain permission data.\n   * Used for header-based permission resolution.\n   */\n  headers?: Record<string, string>;\n}\n\n/**\n * Result of permission-aware bundle creation, including the resolved permissions.\n */\nexport interface PermissionAwareBundle {\n  /**\n   * The MCP server instance for this client.\n   */\n  server: McpServer;\n\n  /**\n   * The orchestrator managing toolsets for this client.\n   */\n  orchestrator: ServerOrchestrator;\n\n  /**\n   * The resolved permissions (allowed toolsets) for this client.\n   * Contains only the toolsets that were successfully enabled.\n   */\n  allowedToolsets: string[];\n\n  /**\n   * Toolsets that failed to enable (e.g., invalid names).\n   * Empty if all requested toolsets were enabled successfully.\n   */\n  failedToolsets: string[];\n}\n\n/**\n * Creates a permission-aware bundle creation function that wraps the original\n * createBundle function with permission resolution and enforcement.\n *\n * This function resolves client permissions and passes them to the bundle creator,\n * which creates a server with STATIC mode configured to only those toolsets.\n *\n * @param originalCreateBundle - Bundle creation function that accepts allowed toolsets\n * @param permissionResolver - Resolver instance for determining client permissions\n * @returns Enhanced bundle creation function that accepts client context\n */\nexport function createPermissionAwareBundle(\n  originalCreateBundle: (allowedToolsets: string[]) => {\n    server: McpServer;\n    orchestrator: ServerOrchestrator;\n  },\n  permissionResolver: PermissionResolver\n) {\n  /**\n   * Creates a server bundle with permission-based toolset access control.\n   * Resolves client permissions and creates a server with those toolsets pre-loaded.\n   *\n   * This function is async to ensure toolsets are fully loaded before the server\n   * is connected to a transport.\n   *\n   * @param context - Client request context containing ID and headers\n   * @returns Promise resolving to server bundle with resolved permissions\n   * @throws {Error} If all requested toolsets fail to enable\n   */\n  return async (\n    context: ClientRequestContext\n  ): Promise<PermissionAwareBundle> => {\n    // Resolve permissions for this client\n    const requestedToolsets = permissionResolver.resolvePermissions(\n      context.clientId,\n      context.headers\n    );\n\n    // Create bundle with allowed toolsets (STATIC mode pre-loads them)\n    const bundle = originalCreateBundle(requestedToolsets);\n\n    // Wait for toolsets to be enabled before returning\n    // This ensures tools are registered before the server connects to transport\n    const manager = bundle.orchestrator.getManager();\n\n    const enabledToolsets: string[] = [];\n    const failedToolsets: string[] = [];\n\n    if (requestedToolsets.length > 0) {\n      const result = await manager.enableToolsets(requestedToolsets);\n\n      // Collect successful and failed toolsets\n      for (const r of result.results) {\n        if (r.success) {\n          enabledToolsets.push(r.name);\n        } else {\n          failedToolsets.push(r.name);\n          console.warn(\n            `Failed to enable toolset '${r.name}' for client '${context.clientId}': ${r.message}`\n          );\n        }\n      }\n\n      // If ALL toolsets failed, this is likely a configuration error\n      if (enabledToolsets.length === 0 && failedToolsets.length > 0) {\n        throw new Error(\n          `All requested toolsets failed to enable for client '${context.clientId}'. ` +\n            `Requested: [${requestedToolsets.join(\", \")}]. ` +\n            `Check that toolset names in permissions match the catalog.`\n        );\n      }\n    }\n\n    // Return bundle with resolved permissions\n    return {\n      server: bundle.server,\n      orchestrator: bundle.orchestrator,\n      allowedToolsets: enabledToolsets,\n      failedToolsets,\n    };\n  };\n}\n","import Fastify, {\n  type FastifyInstance,\n  type FastifyReply,\n  type FastifyRequest,\n} from \"fastify\";\nimport cors from \"@fastify/cors\";\nimport { randomUUID } from \"node:crypto\";\nimport type { DynamicToolManager } from \"../core/DynamicToolManager.js\";\nimport { ClientResourceCache } from \"../session/ClientResourceCache.js\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport { isInitializeRequest } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { ServerOrchestrator } from \"../core/ServerOrchestrator.js\";\nimport type {\n  ClientRequestContext,\n  PermissionAwareBundle,\n} from \"./createPermissionAwareBundle.js\";\nimport type { CustomEndpointDefinition } from \"../http/customEndpoints.js\";\nimport { registerCustomEndpoints } from \"../http/endpointRegistration.js\";\n\nexport interface PermissionAwareFastifyTransportOptions {\n  host?: string;\n  port?: number;\n  basePath?: string;\n  cors?: boolean;\n  logger?: boolean;\n  app?: FastifyInstance;\n  /**\n   * Optional custom HTTP endpoints to register alongside MCP protocol endpoints.\n   * Allows adding REST-like endpoints with Zod validation and type inference.\n   * Handlers receive permission context (allowedToolsets, failedToolsets).\n   */\n  customEndpoints?: CustomEndpointDefinition[];\n}\n\n/**\n * Enhanced Fastify transport that supports permission-based toolset access.\n * Integrates with PermissionResolver to enforce per-client toolset permissions.\n * \n * This transport extracts client context from requests and passes it to the\n * permission-aware bundle creator, ensuring each client receives only their\n * authorized toolsets while maintaining session management and caching.\n */\nexport class PermissionAwareFastifyTransport {\n  private readonly options: {\n    host: string;\n    port: number;\n    basePath: string;\n    cors: boolean;\n    logger: boolean;\n    app?: FastifyInstance;\n    customEndpoints?: CustomEndpointDefinition[];\n  };\n  private readonly defaultManager: DynamicToolManager;\n  private readonly createPermissionAwareBundle: (\n    context: ClientRequestContext\n  ) => Promise<PermissionAwareBundle>;\n  private app: FastifyInstance | null = null;\n  private readonly configSchema?: object;\n\n  // Per-client server bundles and per-client session transports\n  private readonly clientCache = new ClientResourceCache<{\n    server: McpServer;\n    orchestrator: ServerOrchestrator;\n    sessions: Map<string, StreamableHTTPServerTransport>;\n    allowedToolsets: string[];\n    failedToolsets: string[];\n  }>({\n    onEvict: (_key, bundle) => {\n      // Clean up all sessions when a client bundle is evicted\n      this.#cleanupBundle(bundle);\n    },\n  });\n\n  /**\n   * Creates a new PermissionAwareFastifyTransport instance.\n   * @param defaultManager - Default tool manager for status endpoints\n   * @param createPermissionAwareBundle - Function to create permission-aware bundles\n   * @param options - Transport configuration options\n   * @param configSchema - Optional JSON schema for configuration discovery\n   */\n  constructor(\n    defaultManager: DynamicToolManager,\n    createPermissionAwareBundle: (\n      context: ClientRequestContext\n    ) => Promise<PermissionAwareBundle>,\n    options: PermissionAwareFastifyTransportOptions = {},\n    configSchema?: object\n  ) {\n    this.defaultManager = defaultManager;\n    this.createPermissionAwareBundle = createPermissionAwareBundle;\n    this.options = {\n      host: options.host ?? \"0.0.0.0\",\n      port: options.port ?? 3000,\n      basePath: options.basePath ?? \"/\",\n      cors: options.cors ?? true,\n      logger: options.logger ?? false,\n      app: options.app,\n      customEndpoints: options.customEndpoints,\n    };\n    this.configSchema = configSchema;\n  }\n\n  /**\n   * Starts the Fastify server and registers all MCP endpoints.\n   * Sets up routes for health checks, tool status, and MCP protocol handling.\n   */\n  public async start(): Promise<void> {\n    if (this.app) return;\n    const app = this.options.app ?? Fastify({ logger: this.options.logger });\n    if (this.options.cors) {\n      await app.register(cors, { origin: true });\n    }\n\n    const base = this.#normalizeBasePath(this.options.basePath);\n\n    this.#registerHealthEndpoint(app, base);\n    this.#registerToolsEndpoint(app, base);\n    this.#registerConfigDiscoveryEndpoint(app, base);\n    this.#registerMcpPostEndpoint(app, base);\n    this.#registerMcpGetEndpoint(app, base);\n    this.#registerMcpDeleteEndpoint(app, base);\n\n    // Register custom endpoints if provided with permission context\n    // IMPORTANT: Only register if customEndpoints is provided AND has items\n    if (this.options.customEndpoints && this.options.customEndpoints.length > 0) {\n      registerCustomEndpoints(app, base, this.options.customEndpoints, {\n        contextExtractor: async (req) => {\n          // Extract client context from request\n          const context = this.#extractClientContext(req);\n\n          // Resolve permissions for this client\n          try {\n            const bundle = await this.createPermissionAwareBundle(context);\n            return {\n              allowedToolsets: bundle.allowedToolsets,\n              failedToolsets: bundle.failedToolsets,\n            };\n          } catch (error) {\n            // If permission resolution fails, return empty permissions\n            console.warn(\n              `Permission resolution failed for custom endpoint: ${error}`\n            );\n            return {\n              allowedToolsets: [],\n              failedToolsets: [],\n            };\n          }\n        },\n      });\n    }\n\n    // Only listen if we created the app\n    if (!this.options.app) {\n      await app.listen({ host: this.options.host, port: this.options.port });\n    }\n    this.app = app;\n  }\n\n  /**\n   * Stops the Fastify server and cleans up all resources.\n   * Closes all client sessions and clears the cache.\n   */\n  public async stop(): Promise<void> {\n    if (!this.app) return;\n\n    // Stop the cache pruning interval and clear all entries (triggers cleanup)\n    this.clientCache.stop(true);\n\n    if (!this.options.app) {\n      await this.app.close();\n    }\n    this.app = null;\n  }\n\n  /**\n   * Cleans up resources associated with a client bundle.\n   * Closes all sessions within the bundle.\n   * @param bundle - The client bundle to clean up\n   * @private\n   */\n  #cleanupBundle(bundle: {\n    server: McpServer;\n    orchestrator: ServerOrchestrator;\n    sessions: Map<string, StreamableHTTPServerTransport>;\n    allowedToolsets: string[];\n    failedToolsets: string[];\n  }): void {\n    for (const [sessionId, transport] of bundle.sessions.entries()) {\n      try {\n        if (typeof (transport as any).close === \"function\") {\n          (transport as any).close().catch((err: unknown) => {\n            console.warn(`Error closing session ${sessionId}:`, err);\n          });\n        }\n      } catch (err) {\n        console.warn(`Error closing session ${sessionId}:`, err);\n      }\n    }\n    bundle.sessions.clear();\n  }\n\n  /**\n   * Normalizes the base path by removing trailing slashes.\n   * @param basePath - The base path to normalize\n   * @returns Normalized base path without trailing slash\n   * @private\n   */\n  #normalizeBasePath(basePath: string): string {\n    return basePath.endsWith(\"/\") ? basePath.slice(0, -1) : basePath;\n  }\n\n  /**\n   * Registers the health check endpoint.\n   * @param app - Fastify instance\n   * @param base - Base path for routes\n   * @private\n   */\n  #registerHealthEndpoint(app: FastifyInstance, base: string): void {\n    app.get(`${base}/healthz`, async () => ({ ok: true }));\n  }\n\n  /**\n   * Registers the tools status endpoint.\n   * @param app - Fastify instance\n   * @param base - Base path for routes\n   * @private\n   */\n  #registerToolsEndpoint(app: FastifyInstance, base: string): void {\n    app.get(`${base}/tools`, async () => this.defaultManager.getStatus());\n  }\n\n  /**\n   * Registers the MCP configuration discovery endpoint.\n   * @param app - Fastify instance\n   * @param base - Base path for routes\n   * @private\n   */\n  #registerConfigDiscoveryEndpoint(app: FastifyInstance, base: string): void {\n    app.get(`${base}/.well-known/mcp-config`, async (_req, reply) => {\n      reply.header(\"Content-Type\", \"application/schema+json; charset=utf-8\");\n      const baseSchema = this.configSchema ?? {\n        $schema: \"https://json-schema.org/draft/2020-12/schema\",\n        title: \"MCP Session Configuration\",\n        description: \"Schema for the /mcp endpoint configuration\",\n        type: \"object\",\n        properties: {},\n        required: [],\n        \"x-mcp-version\": \"1.0\",\n        \"x-query-style\": \"dot+bracket\",\n      };\n      return baseSchema;\n    });\n  }\n\n  /**\n   * Registers the POST /mcp endpoint for JSON-RPC requests.\n   * Extracts client context, resolves permissions, and handles MCP protocol.\n   * @param app - Fastify instance\n   * @param base - Base path for routes\n   * @private\n   */\n  #registerMcpPostEndpoint(app: FastifyInstance, base: string): void {\n    app.post(\n      `${base}/mcp`,\n      async (req: FastifyRequest, reply: FastifyReply) => {\n        // Extract client context from request\n        const context = this.#extractClientContext(req);\n\n        // Determine if we should cache this client's bundle\n        const useCache = !context.clientId.startsWith(\"anon-\");\n\n        // Get or create permission-aware bundle for this client\n        let bundle = useCache ? this.clientCache.get(context.clientId) : null;\n        if (!bundle) {\n          try {\n            const created = await this.createPermissionAwareBundle(context);\n\n            // Log any failed toolsets for debugging\n            if (created.failedToolsets.length > 0) {\n              console.warn(\n                `Client ${context.clientId} had ${created.failedToolsets.length} toolsets fail to enable: ` +\n                  `[${created.failedToolsets.join(\", \")}]. ` +\n                  `Successfully enabled: [${created.allowedToolsets.join(\", \")}]`\n              );\n            }\n\n            const providedSessions = (created as { sessions?: Map<string, StreamableHTTPServerTransport> }).sessions;\n            bundle = {\n              server: created.server,\n              orchestrator: created.orchestrator,\n              allowedToolsets: created.allowedToolsets,\n              failedToolsets: created.failedToolsets,\n              sessions:\n                providedSessions instanceof Map ? providedSessions : new Map(),\n            };\n            if (useCache) this.clientCache.set(context.clientId, bundle);\n          } catch (error) {\n            // Handle permission resolution or bundle creation failures\n            console.error(\n              `Failed to create permission-aware bundle for client ${context.clientId}:`,\n              error\n            );\n            reply.code(403);\n            return this.#createSafeErrorResponse(\"Access denied\");\n          }\n        }\n\n        const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n\n        let transport: StreamableHTTPServerTransport | undefined;\n        if (sessionId && bundle.sessions.get(sessionId)) {\n          transport = bundle.sessions.get(sessionId)!;\n        } else if (!sessionId && isInitializeRequest((req as any).body)) {\n          const newSessionId = randomUUID();\n          transport = new StreamableHTTPServerTransport({\n            sessionIdGenerator: () => newSessionId,\n            onsessioninitialized: (sid: string) => {\n              bundle!.sessions.set(sid, transport!);\n            },\n          });\n          try {\n            await bundle.server.connect(transport);\n          } catch (error) {\n            reply.code(500);\n            return {\n              jsonrpc: \"2.0\",\n              error: { code: -32603, message: \"Error initializing server.\" },\n              id: null,\n            };\n          }\n          transport.onclose = () => {\n            if (transport?.sessionId)\n              bundle!.sessions.delete(transport.sessionId);\n          };\n        } else {\n          reply.code(400);\n          return {\n            jsonrpc: \"2.0\",\n            error: { code: -32000, message: \"Session not found or expired\" },\n            id: null,\n          };\n        }\n\n        // Delegate handling to SDK transport using raw Node req/res\n        await transport.handleRequest(\n          (req as any).raw,\n          (reply as any).raw,\n          (req as any).body\n        );\n        return reply;\n      }\n    );\n  }\n\n  /**\n   * Registers the GET /mcp endpoint for SSE notifications.\n   * @param app - Fastify instance\n   * @param base - Base path for routes\n   * @private\n   */\n  #registerMcpGetEndpoint(app: FastifyInstance, base: string): void {\n    app.get(`${base}/mcp`, async (req: FastifyRequest, reply: FastifyReply) => {\n      const clientIdHeader = (\n        req.headers[\"mcp-client-id\"] as string | undefined\n      )?.trim();\n      const clientId =\n        clientIdHeader && clientIdHeader.length > 0 ? clientIdHeader : \"\";\n      if (!clientId) {\n        reply.code(400);\n        return \"Missing mcp-client-id\";\n      }\n      const bundle = this.clientCache.get(clientId);\n      if (!bundle) {\n        reply.code(400);\n        return \"Invalid or expired client\";\n      }\n      const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n      if (!sessionId) {\n        reply.code(400);\n        return \"Missing mcp-session-id\";\n      }\n      const transport = bundle.sessions.get(sessionId);\n      if (!transport) {\n        reply.code(400);\n        return \"Invalid or expired session ID\";\n      }\n      await transport.handleRequest((req as any).raw, (reply as any).raw);\n      return reply;\n    });\n  }\n\n  /**\n   * Registers the DELETE /mcp endpoint for session termination.\n   * @param app - Fastify instance\n   * @param base - Base path for routes\n   * @private\n   */\n  #registerMcpDeleteEndpoint(app: FastifyInstance, base: string): void {\n    app.delete(\n      `${base}/mcp`,\n      async (req: FastifyRequest, reply: FastifyReply) => {\n        const clientIdHeader = (\n          req.headers[\"mcp-client-id\"] as string | undefined\n        )?.trim();\n        const clientId =\n          clientIdHeader && clientIdHeader.length > 0 ? clientIdHeader : \"\";\n        const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n        if (!clientId || !sessionId) {\n          reply.code(400);\n          return {\n            jsonrpc: \"2.0\",\n            error: {\n              code: -32600,\n              message: \"Missing mcp-client-id or mcp-session-id header\",\n            },\n            id: null,\n          };\n        }\n        const bundle = this.clientCache.get(clientId);\n        const transport = bundle?.sessions.get(sessionId);\n        if (!bundle || !transport) {\n          reply.code(404);\n          return {\n            jsonrpc: \"2.0\",\n            error: { code: -32000, message: \"Session not found or expired\" },\n            id: null,\n          };\n        }\n        try {\n          // Best-effort close and evict\n          if (typeof (transport as any).close === \"function\") {\n            try {\n              await (transport as any).close();\n            } catch {}\n          }\n        } finally {\n          if (transport?.sessionId) bundle.sessions.delete(transport.sessionId);\n          else bundle.sessions.delete(sessionId);\n        }\n        reply.code(204).send();\n        return reply;\n      }\n    );\n  }\n\n  /**\n   * Extracts client context from the request.\n   * Generates anonymous client ID if not provided in headers.\n   * @param req - Fastify request object\n   * @returns Client request context with ID and headers\n   * @private\n   */\n  #extractClientContext(req: FastifyRequest): ClientRequestContext {\n    const clientIdHeader = (\n      req.headers[\"mcp-client-id\"] as string | undefined\n    )?.trim();\n    const clientId =\n      clientIdHeader && clientIdHeader.length > 0\n        ? clientIdHeader\n        : `anon-${randomUUID()}`;\n\n    // Convert headers to plain object for permission resolution\n    const headers: Record<string, string> = {};\n    for (const [key, value] of Object.entries(req.headers)) {\n      if (typeof value === \"string\") {\n        headers[key] = value;\n      }\n    }\n\n    return { clientId, headers };\n  }\n\n  /**\n   * Creates a safe error response that doesn't expose unauthorized toolset information.\n   * Used for permission-related errors to prevent information leakage.\n   * @param message - Generic error message to return to client\n   * @param code - JSON-RPC error code (default: -32000 for server error)\n   * @returns JSON-RPC error response object\n   * @private\n   */\n  #createSafeErrorResponse(message: string = \"Access denied\", code: number = -32000) {\n    return {\n      jsonrpc: \"2.0\" as const,\n      error: {\n        code,\n        message,\n      },\n      id: null,\n    };\n  }\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type {\n  CreatePermissionBasedMcpServerOptions,\n  ExposurePolicy,\n} from \"../types/index.js\";\nimport { validatePermissionConfig } from \"../permissions/validatePermissionConfig.js\";\nimport { PermissionResolver } from \"../permissions/PermissionResolver.js\";\nimport { ServerOrchestrator } from \"../core/ServerOrchestrator.js\";\nimport { createPermissionAwareBundle } from \"../permissions/createPermissionAwareBundle.js\";\nimport { PermissionAwareFastifyTransport } from \"../permissions/PermissionAwareFastifyTransport.js\";\n\n/**\n * Validates and sanitizes exposure policy for permission-based servers.\n * Certain policy options are not applicable or could conflict with permission-based access control.\n * @param policy - The original exposure policy\n * @returns Sanitized policy safe for permission-based servers\n * @private\n */\nfunction sanitizeExposurePolicyForPermissions(\n  policy?: ExposurePolicy\n): ExposurePolicy | undefined {\n  if (!policy) return undefined;\n\n  const sanitized: ExposurePolicy = {\n    namespaceToolsWithSetKey: policy.namespaceToolsWithSetKey,\n  };\n\n  // Warn about ignored options\n  if (policy.allowlist !== undefined) {\n    console.warn(\n      \"Permission-based servers: exposurePolicy.allowlist is ignored. \" +\n        \"Allowed toolsets are determined by client permissions.\"\n    );\n  }\n  if (policy.denylist !== undefined) {\n    console.warn(\n      \"Permission-based servers: exposurePolicy.denylist is ignored. \" +\n        \"Use permission configuration to control toolset access.\"\n    );\n  }\n  if (policy.maxActiveToolsets !== undefined) {\n    console.warn(\n      \"Permission-based servers: exposurePolicy.maxActiveToolsets is ignored. \" +\n        \"Toolset count is determined by client permissions.\"\n    );\n  }\n  if (policy.onLimitExceeded !== undefined) {\n    console.warn(\n      \"Permission-based servers: exposurePolicy.onLimitExceeded is ignored. \" +\n        \"No toolset limits are enforced.\"\n    );\n  }\n\n  return sanitized;\n}\n\n/**\n * Creates an MCP server with permission-based toolset access control.\n *\n * This function provides a separate API for creating servers where each client receives\n * only the toolsets they're authorized to access. Each client gets a fresh server instance\n * with STATIC mode configured to their allowed toolsets, ensuring per-client isolation\n * without meta-tools or dynamic loading.\n *\n * The server supports two permission sources:\n * - **Header-based**: Permissions are read from request headers (e.g., `mcp-toolset-permissions`)\n * - **Config-based**: Permissions are resolved server-side using static maps or resolver functions\n *\n * @param options - Configuration options including permission settings, catalog, and HTTP transport options\n * @returns Server instance with `server`, `start()`, and `close()` methods matching the createMcpServer interface\n * @throws {Error} If permission configuration is invalid, missing, or if startup.mode is provided\n *\n * @example\n * // Header-based permissions\n * const server = await createPermissionBasedMcpServer({\n *   createServer: () => new McpServer({ name: \"my-server\", version: \"1.0.0\" }),\n *   catalog: { toolsetA: { name: \"Toolset A\", tools: [...] } },\n *   permissions: {\n *     source: 'headers',\n *     headerName: 'mcp-toolset-permissions' // optional, this is the default\n *   }\n * });\n *\n * @example\n * // Config-based permissions with static map\n * const server = await createPermissionBasedMcpServer({\n *   createServer: () => new McpServer({ name: \"my-server\", version: \"1.0.0\" }),\n *   catalog: { toolsetA: { name: \"Toolset A\", tools: [...] } },\n *   permissions: {\n *     source: 'config',\n *     staticMap: {\n *       'client-1': ['toolsetA', 'toolsetB'],\n *       'client-2': ['toolsetC']\n *     },\n *     defaultPermissions: [] // optional, defaults to empty array\n *   }\n * });\n *\n * @example\n * // Config-based permissions with resolver function\n * const server = await createPermissionBasedMcpServer({\n *   createServer: () => new McpServer({ name: \"my-server\", version: \"1.0.0\" }),\n *   catalog: { toolsetA: { name: \"Toolset A\", tools: [...] } },\n *   permissions: {\n *     source: 'config',\n *     resolver: (clientId) => {\n *       // Your custom logic to determine permissions\n *       return clientId.startsWith('admin-') ? ['toolsetA', 'toolsetB'] : ['toolsetA'];\n *     },\n *     defaultPermissions: ['toolsetA'] // fallback if resolver fails\n *   }\n * });\n */\nexport async function createPermissionBasedMcpServer(\n  options: CreatePermissionBasedMcpServerOptions\n) {\n  // Validate that permissions field is provided\n  if (!options.permissions) {\n    throw new Error(\n      \"Permission configuration is required for createPermissionBasedMcpServer. \" +\n        \"Please provide a 'permissions' field in the options.\"\n    );\n  }\n\n  // Validate permission configuration\n  validatePermissionConfig(options.permissions);\n\n  // Prevent startup.mode configuration - permissions determine toolsets\n  if ((options as any).startup) {\n    throw new Error(\n      \"Permission-based servers determine toolsets from client permissions. \" +\n        \"The 'startup' option is not allowed. Remove it from your configuration.\"\n    );\n  }\n\n  // Validate createServer factory is provided\n  if (typeof options.createServer !== \"function\") {\n    throw new Error(\n      \"createPermissionBasedMcpServer: `createServer` (factory) is required\"\n    );\n  }\n\n  // Sanitize exposure policy for permission-based operation\n  const sanitizedPolicy = sanitizeExposurePolicyForPermissions(\n    options.exposurePolicy\n  );\n\n  // Create permission resolver instance\n  const permissionResolver = new PermissionResolver(options.permissions);\n\n  // Create base server for default manager (used for status endpoints)\n  const baseServer: McpServer = options.createServer();\n\n  // Create base orchestrator for default manager (empty toolsets for status endpoint)\n  // No notifier needed - STATIC mode with fixed toolsets per client\n  const baseOrchestrator = new ServerOrchestrator({\n    server: baseServer,\n    catalog: options.catalog,\n    moduleLoaders: options.moduleLoaders,\n    exposurePolicy: sanitizedPolicy,\n    context: options.context,\n    notifyToolsListChanged: undefined, // No notifications in STATIC mode\n    startup: { mode: \"STATIC\", toolsets: [] },\n    registerMetaTools: false,\n  });\n\n  // Create permission-aware bundle creator\n  const createBundle = createPermissionAwareBundle(\n    (allowedToolsets: string[]) => {\n      // Create fresh server and orchestrator for each client\n      // Use STATIC mode but don't auto-enable toolsets in constructor\n      // We'll enable them manually in createPermissionAwareBundle to ensure they're loaded before connection\n      const clientServer: McpServer = options.createServer();\n      const clientOrchestrator = new ServerOrchestrator({\n        server: clientServer,\n        catalog: options.catalog,\n        moduleLoaders: options.moduleLoaders,\n        exposurePolicy: sanitizedPolicy,\n        context: options.context,\n        notifyToolsListChanged: undefined, // No notifications in STATIC mode\n        startup: { mode: \"STATIC\", toolsets: [] }, // Empty - we'll enable manually\n        registerMetaTools: false, // No meta-tools - toolsets are fixed per client\n      });\n      return { server: clientServer, orchestrator: clientOrchestrator };\n    },\n    permissionResolver\n  );\n\n  // Create permission-aware transport\n  const transport = new PermissionAwareFastifyTransport(\n    baseOrchestrator.getManager(),\n    createBundle,\n    options.http,\n    options.configSchema\n  );\n\n  // Return same interface as createMcpServer\n  return {\n    server: baseServer,\n    start: async () => {\n      await transport.start();\n    },\n    close: async () => {\n      try {\n        // Stop the transport (cleans up client contexts)\n        await transport.stop();\n      } finally {\n        // Clear permission cache\n        permissionResolver.clearCache();\n      }\n    },\n  };\n}\n","import { z } from \"zod\";\n\n/**\n * Supported HTTP methods for custom endpoints\n */\nexport type HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\";\n\n/**\n * Request context passed to custom endpoint handlers.\n * Contains validated and typed request data without exposing Fastify types.\n */\nexport interface CustomEndpointRequest<\n  TBody = unknown,\n  TQuery = unknown,\n  TParams = unknown\n> {\n  /**\n   * Validated request body (typed from bodySchema)\n   */\n  body: TBody;\n\n  /**\n   * Validated query parameters (typed from querySchema)\n   */\n  query: TQuery;\n\n  /**\n   * Validated path parameters (typed from paramsSchema)\n   */\n  params: TParams;\n\n  /**\n   * Raw request headers\n   */\n  headers: Record<string, string | string[] | undefined>;\n\n  /**\n   * Client ID (from mcp-client-id header or auto-generated for anonymous clients)\n   */\n  clientId: string;\n}\n\n/**\n * Permission-aware request context for custom endpoints in permission-based servers.\n * Extends CustomEndpointRequest with permission information.\n */\nexport interface PermissionAwareEndpointRequest<\n  TBody = unknown,\n  TQuery = unknown,\n  TParams = unknown\n> extends CustomEndpointRequest<TBody, TQuery, TParams> {\n  /**\n   * Toolsets this client is allowed to access (resolved from permissions)\n   */\n  allowedToolsets: string[];\n\n  /**\n   * Toolsets that failed to enable for this client\n   */\n  failedToolsets: string[];\n}\n\n/**\n * Handler function type with automatic type inference from Zod schemas.\n * Receives validated and typed request data, returns typed response.\n */\nexport type CustomEndpointHandler<\n  TBody extends z.ZodTypeAny,\n  TQuery extends z.ZodTypeAny,\n  TParams extends z.ZodTypeAny,\n  TResponse extends z.ZodTypeAny\n> = (\n  request: CustomEndpointRequest<\n    TBody extends z.ZodTypeAny ? z.infer<TBody> : never,\n    TQuery extends z.ZodTypeAny ? z.infer<TQuery> : never,\n    TParams extends z.ZodTypeAny ? z.infer<TParams> : never\n  >\n) => Promise<z.infer<TResponse>> | z.infer<TResponse>;\n\n/**\n * Permission-aware handler function type for permission-based servers.\n * Receives permission context in addition to validated request data.\n */\nexport type PermissionAwareEndpointHandler<\n  TBody extends z.ZodTypeAny,\n  TQuery extends z.ZodTypeAny,\n  TParams extends z.ZodTypeAny,\n  TResponse extends z.ZodTypeAny\n> = (\n  request: PermissionAwareEndpointRequest<\n    TBody extends z.ZodTypeAny ? z.infer<TBody> : never,\n    TQuery extends z.ZodTypeAny ? z.infer<TQuery> : never,\n    TParams extends z.ZodTypeAny ? z.infer<TParams> : never\n  >\n) => Promise<z.infer<TResponse>> | z.infer<TResponse>;\n\n/**\n * Custom HTTP endpoint definition with Zod schema-based validation and type inference.\n * Allows defining REST-like endpoints alongside MCP protocol endpoints.\n *\n * @template TBody - Zod schema for request body validation\n * @template TQuery - Zod schema for query parameter validation\n * @template TParams - Zod schema for path parameter validation\n * @template TResponse - Zod schema for response validation\n *\n * @example\n * ```typescript\n * const getUserEndpoint = defineEndpoint({\n *   method: \"GET\",\n *   path: \"/users/:userId\",\n *   paramsSchema: z.object({\n *     userId: z.string().uuid(),\n *   }),\n *   responseSchema: z.object({\n *     id: z.string(),\n *     name: z.string(),\n *   }),\n *   handler: async (req) => {\n *     // req.params is typed: { userId: string }\n *     const { userId } = req.params;\n *     return { id: userId, name: \"Alice\" };\n *   },\n * });\n * ```\n */\nexport interface CustomEndpointDefinition<\n  TBody extends z.ZodTypeAny = z.ZodTypeAny,\n  TQuery extends z.ZodTypeAny = z.ZodTypeAny,\n  TParams extends z.ZodTypeAny = z.ZodTypeAny,\n  TResponse extends z.ZodTypeAny = z.ZodTypeAny\n> {\n  /**\n   * HTTP method for this endpoint\n   */\n  method: HttpMethod;\n\n  /**\n   * URL path (relative to basePath). Supports path parameters using :param syntax.\n   *\n   * @example\n   * - \"/users\" - Simple path\n   * - \"/users/:id\" - Path with single parameter\n   * - \"/items/:category/:id\" - Path with multiple parameters\n   */\n  path: string;\n\n  /**\n   * Optional Zod schema for request body validation (typically used with POST, PUT, PATCH).\n   * Enables automatic type inference for handler body parameter.\n   */\n  bodySchema?: TBody;\n\n  /**\n   * Optional Zod schema for query parameter validation.\n   * Enables automatic type inference for handler query parameter.\n   *\n   * @example\n   * ```typescript\n   * querySchema: z.object({\n   *   limit: z.coerce.number().int().positive().default(10),\n   *   offset: z.coerce.number().int().nonnegative().default(0),\n   * })\n   * ```\n   */\n  querySchema?: TQuery;\n\n  /**\n   * Optional Zod schema for path parameter validation.\n   * Enables automatic type inference for handler params parameter.\n   */\n  paramsSchema?: TParams;\n\n  /**\n   * Optional Zod schema for response validation.\n   * Enables automatic type inference for handler return type.\n   * If validation fails, returns 500 error to prevent information leakage.\n   */\n  responseSchema?: TResponse;\n\n  /**\n   * Request handler function with inferred types from schemas.\n   * Receives validated and typed request data, returns typed response.\n   */\n  handler: CustomEndpointHandler<TBody, TQuery, TParams, TResponse>;\n\n  /**\n   * Optional description for documentation purposes\n   */\n  description?: string;\n}\n\n/**\n * Standard error response structure for custom endpoints\n */\nexport interface EndpointErrorResponse {\n  error: {\n    /**\n     * Error code indicating the type of error\n     * - VALIDATION_ERROR: Request validation failed (400)\n     * - INTERNAL_ERROR: Handler threw an error (500)\n     * - RESPONSE_VALIDATION_ERROR: Response validation failed (500)\n     */\n    code: \"VALIDATION_ERROR\" | \"INTERNAL_ERROR\" | \"RESPONSE_VALIDATION_ERROR\";\n\n    /**\n     * Human-readable error message\n     */\n    message: string;\n\n    /**\n     * Optional additional error details (e.g., Zod validation errors)\n     */\n    details?: unknown;\n  };\n}\n\n/**\n * Helper function to create type-safe custom endpoints with automatic type inference.\n * Provides better IntelliSense and type checking for endpoint definitions.\n *\n * @template TBody - Zod schema for request body\n * @template TQuery - Zod schema for query parameters\n * @template TParams - Zod schema for path parameters\n * @template TResponse - Zod schema for response\n *\n * @param definition - Endpoint definition with schemas and handler\n * @returns The same endpoint definition with full type inference\n *\n * @example\n * ```typescript\n * import { z } from \"zod\";\n * import { defineEndpoint } from \"toolception\";\n *\n * const getUsersEndpoint = defineEndpoint({\n *   method: \"GET\",\n *   path: \"/users\",\n *   querySchema: z.object({\n *     limit: z.coerce.number().int().positive().default(10),\n *     role: z.enum([\"admin\", \"user\"]).optional(),\n *   }),\n *   responseSchema: z.object({\n *     users: z.array(z.object({\n *       id: z.string(),\n *       name: z.string(),\n *     })),\n *     total: z.number(),\n *   }),\n *   handler: async (req) => {\n *     // req.query is fully typed: { limit: number, role?: \"admin\" | \"user\" }\n *     const { limit, role } = req.query;\n *\n *     return {\n *       users: [{ id: \"1\", name: \"Alice\" }],\n *       total: 1,\n *     };\n *   },\n * });\n * ```\n */\nexport function defineEndpoint<\n  TBody extends z.ZodTypeAny = z.ZodNever,\n  TQuery extends z.ZodTypeAny = z.ZodNever,\n  TParams extends z.ZodTypeAny = z.ZodNever,\n  TResponse extends z.ZodTypeAny = z.ZodAny\n>(\n  definition: CustomEndpointDefinition<TBody, TQuery, TParams, TResponse>\n): CustomEndpointDefinition<TBody, TQuery, TParams, TResponse> {\n  return definition;\n}\n\n/**\n * Helper function to create permission-aware custom endpoints for permission-based servers.\n * Similar to defineEndpoint but with access to permission context in the handler.\n *\n * @template TBody - Zod schema for request body\n * @template TQuery - Zod schema for query parameters\n * @template TParams - Zod schema for path parameters\n * @template TResponse - Zod schema for response\n *\n * @param definition - Endpoint definition with permission-aware handler\n * @returns Endpoint definition compatible with permission-based servers\n *\n * @example\n * ```typescript\n * import { definePermissionAwareEndpoint } from \"toolception\";\n *\n * const statsEndpoint = definePermissionAwareEndpoint({\n *   method: \"GET\",\n *   path: \"/my-permissions\",\n *   responseSchema: z.object({\n *     toolsets: z.array(z.string()),\n *     count: z.number(),\n *   }),\n *   handler: async (req) => {\n *     // req.allowedToolsets and req.failedToolsets are available\n *     return {\n *       toolsets: req.allowedToolsets,\n *       count: req.allowedToolsets.length,\n *     };\n *   },\n * });\n * ```\n */\nexport function definePermissionAwareEndpoint<\n  TBody extends z.ZodTypeAny = z.ZodNever,\n  TQuery extends z.ZodTypeAny = z.ZodNever,\n  TParams extends z.ZodTypeAny = z.ZodNever,\n  TResponse extends z.ZodTypeAny = z.ZodAny\n>(definition: {\n  method: HttpMethod;\n  path: string;\n  bodySchema?: TBody;\n  querySchema?: TQuery;\n  paramsSchema?: TParams;\n  responseSchema?: TResponse;\n  handler: PermissionAwareEndpointHandler<TBody, TQuery, TParams, TResponse>;\n  description?: string;\n}): CustomEndpointDefinition<TBody, TQuery, TParams, TResponse> {\n  // Internal conversion: permission-aware handler is compatible with standard handler\n  // The permission fields will be injected by the registration logic\n  return definition as any;\n}\n"]}