""" MCP server definition: registers tools and resources exposed by the fabric-config-oracle over stdio transport. Tools ----- * `get_optimal_fabric_config` — resolve the optimal Fabric build config for a specific or the latest Minecraft version, applying snapshot fallback logic. * `generate_gradle_properties` — render a complete `gradle.properties` file text from a resolved config plus mod metadata. Resources --------- * `fabric://config/schema/gradle_properties` — static reference schema for a well-formed Fabric `gradle.properties` file. """ from __future__ import annotations import logging from typing import Optional from mcp.server.fastmcp import FastMCP from models import FabricConfig from resolver import VersionResolver logger = logging.getLogger(__name__) # --------------------------------------------------------------------------- # Server + shared state # --------------------------------------------------------------------------- mcp: FastMCP = FastMCP("fabric-config-oracle") # Module-level singleton resolver. The MCP stdio server runs in a single # process for its entire lifetime, so a single shared HTTP client + cache # is both safe and desirable (connection pooling, fewer upstream calls). _resolver: Optional[VersionResolver] = None def _get_resolver() -> VersionResolver: """Lazily instantiate the shared VersionResolver.""" global _resolver if _resolver is None: _resolver = VersionResolver() return _resolver # --------------------------------------------------------------------------- # Tool 1: get_optimal_fabric_config # --------------------------------------------------------------------------- @mcp.tool() async def get_optimal_fabric_config( minecraft_version: str = "latest", mod_id: Optional[str] = None, ) -> dict: """ Get the optimal Fabric mod configuration for a specific or the latest Minecraft version. When `minecraft_version` is "latest" (or omitted), the server applies intelligent fallback logic: it walks the newest Minecraft snapshots and returns the first one that has full Fabric ecosystem support. **Support is defined differently depending on the MC version:** - For OBFUSCATED versions (1.21.x and earlier): the snapshot must have BOTH Yarn mappings AND a Fabric API build available. - For UNOBFUSCATED versions (26.1+): only a Fabric API build is required. Yarn mappings are deprecated and don't exist for these versions — mods use Mojang's official Mojmap via Loom's `loom.officialMojangMappings()` in build.gradle. If no snapshots have full Fabric support yet, the server falls back to the newest stable release that does. Args: minecraft_version: Target Minecraft version. Accepts: - "latest" (default): apply fallback logic. - Specific releases: "1.21", "1.21.4", "26.1", "26.2". - Specific snapshots: "26.3 Snapshot 2". mod_id: Optional mod identifier. Currently informational only — included in the response payload for caller convenience but does not affect resolution. Returns: A JSON object with the following fields: minecraft_version — the resolved MC version string version_type — "release" or "snapshot" yarn_mappings — Yarn build string (e.g. "1.21.4+build.8"), OR empty string for MC 26.1+ (Yarn deprecated; use Mojmap via Loom) loader_version — Fabric Loader version fabric_api_version — Fabric API mod version from Modrinth loom_version — Fabric Loom Gradle plugin version gradle_wrapper_version — recommended Gradle wrapper version message — human-readable status message mod_id — echoed back if provided """ resolver = _get_resolver() config: FabricConfig = await resolver.resolve_config(minecraft_version) payload = config.model_dump() if mod_id: payload["mod_id"] = mod_id return payload # --------------------------------------------------------------------------- # Tool 2: generate_gradle_properties # --------------------------------------------------------------------------- @mcp.tool() async def generate_gradle_properties( mod_name: str, mod_version: str, minecraft_version: str = "latest", ) -> str: """ Generate the exact text content of a `gradle.properties` file for a Fabric mod, based on the resolved build configuration. Internally calls `get_optimal_fabric_config` to resolve Yarn / Loader / Fabric API versions, then renders a complete properties file with both mod metadata and Fabric tooling versions. Args: mod_name: Human-readable mod name (e.g. "Example Mod"). mod_version: Mod version string (e.g. "1.0.0"). minecraft_version: Target MC version, default "latest". Returns: The full text content of a `gradle.properties` file, ready to be written to disk by the AI assistant. """ resolver = _get_resolver() config: FabricConfig = await resolver.resolve_config(minecraft_version) # Derive a safe mod_id from the mod name: lowercase, replace non-alnum # with hyphens, collapse runs of hyphens, strip leading/trailing hyphens. raw_id = "".join(c.lower() if c.isalnum() else "-" for c in mod_name) while "--" in raw_id: raw_id = raw_id.replace("--", "-") mod_id = raw_id.strip("-") or "mod" maven_group = f"com.example.{mod_id.replace('-', '.')}" archives_base_name = mod_id # For unobfuscated versions (26.1+), Yarn mappings are deprecated and # not used — Loom reads Mojang's official Mojmap directly via # `loom.officialMojangMappings()` in build.gradle. We omit the # `yarn_mappings` line entirely and emit a comment explaining why. from resolver import is_unobfuscated unobf = is_unobfuscated(config.minecraft_version) mc_fabric_section: list[str] = [ "# Minecraft & Fabric versions", f"minecraft_version = {config.minecraft_version}", ] if unobf: mc_fabric_section.append( "# yarn_mappings: not used — MC 26.1+ is unobfuscated; mods" ) mc_fabric_section.append( "# use Mojang official Mojmap via loom.officialMojangMappings()" ) mc_fabric_section.append( "# in build.gradle. (See https://docs.fabricmc.net/develop/porting/mappings)" ) else: mc_fabric_section.append(f"yarn_mappings = {config.yarn_mappings}") mc_fabric_section.append(f"loader_version = {config.loader_version}") mc_fabric_section.append(f"fabric_version = {config.fabric_api_version}") lines: list[str] = [ "# Gradle properties for Fabric mod", "# Generated by fabric-config-oracle MCP server", "", "# Mod metadata", f"mod_name = {mod_name}", f"mod_id = {mod_id}", f"mod_version = {mod_version}", f"maven_group = {maven_group}", f"archives_base_name = {archives_base_name}", "", *mc_fabric_section, "", "# Build tooling", f"loom_version = {config.loom_version}", f"gradle_wrapper_version = {config.gradle_wrapper_version}", "", "# Optional: Kotlin support (uncomment if needed)", "# kotlin_code_style = official", "# fabric_kotlin_version = 1.10.10+kotlin.1.9.10", ] return "\n".join(lines) # --------------------------------------------------------------------------- # Resource: gradle_properties schema # --------------------------------------------------------------------------- _GRADLE_PROPERTIES_SCHEMA = """\ # Fabric gradle.properties Schema Reference # ========================================= # This document describes the expected structure of a Fabric mod's # gradle.properties file. All keys are case-sensitive. # # IMPORTANT: Mappings selection depends on the target Minecraft version: # # * MC <= 1.21.11 (OBFUSCATED): Yarn mappings are REQUIRED. The mod # references obfuscated class names which # Yarn translates to human-readable names. # Loom is configured in build.gradle via # mappings "net.fabricmc:yarn:${yarn_mappings}:v2" # # * MC >= 26.1 (UNOBFUSCATED): Yarn mappings DO NOT EXIST and are # NOT NEEDED. Mojang ships the jar # unobfuscated with parameter names. # Loom is configured in build.gradle via # loom.officialMojangMappings() # The `yarn_mappings` property line is # OMITTED entirely. # # Source: https://docs.fabricmc.net/develop/porting/mappings # --- Common keys (always present) --- # mod_name: Human-readable mod name (string, any characters) mod_name = Example Mod # mod_id: Lowercase identifier, alphanumeric + hyphens only, max 64 chars. mod_id = example-mod # mod_version: SemVer-style version string for the mod itself. mod_version = 1.0.0 # maven_group: Maven group ID for artifact publishing. Conventionally the # reversed domain of the mod author. maven_group = com.example.examplemod # archives_base_name: Base name for built jar artifacts. archives_base_name = example-mod # minecraft_version: Exact MC version this mod targets (e.g. "1.21.4", "26.2"). minecraft_version = 26.2 # loader_version: Fabric Loader version. The mod's minimum compatible loader. loader_version = 0.16.0 # fabric_version: Fabric API mod version from Modrinth. Required if the mod # depends on Fabric API; otherwise can be omitted. fabric_version = 0.155.2+26.2 # --- Keys present ONLY for obfuscated versions (MC <= 1.21.11) --- # yarn_mappings: Yarn mappings version from meta.fabricmc.net. # Format: "+build.". # OMITTED for MC 26.1+ (unobfuscated; use Mojmap instead). yarn_mappings = 1.21.4+build.8 # --- Build tooling (always present) --- # loom_version: Fabric Loom Gradle plugin version (e.g. "1.7-SNAPSHOT"). loom_version = 1.7-SNAPSHOT # gradle_wrapper_version: Recommended Gradle wrapper version. gradle_wrapper_version = 8.8 # --- Optional: Kotlin --- # kotlin_code_style = official # fabric_kotlin_version = 1.10.10+kotlin.1.9.10 """ @mcp.resource("fabric://config/schema/gradle_properties") def get_gradle_properties_schema() -> str: """ Static reference schema for a Fabric mod `gradle.properties` file. Reading this resource returns a plain-text document describing every expected key, its purpose, and an example value. AI assistants can use this to validate generated `gradle.properties` files or to learn the expected structure before generating one. """ return _GRADLE_PROPERTIES_SCHEMA # --------------------------------------------------------------------------- # Public entry point # --------------------------------------------------------------------------- def get_server() -> FastMCP: """Return the configured FastMCP server instance.""" return mcp