Skip to main content
Glama
by frap129
spec.md6.39 kB
# canonical-models Specification ## Purpose Defines the canonical data models used throughout LoreKeeper MCP for representing D&D 5e entities. These models serve as the single source of truth for field names, types, and validation rules, regardless of data source (API or OrcBrew). ## ADDED Requirements ### Requirement: Canonical Model Package Structure The system SHALL provide a dedicated `models/` package containing canonical Pydantic models for all supported D&D entity types. #### Scenario: Import canonical models from top-level package - **GIVEN** a developer needs to use entity models - **WHEN** they import from `lorekeeper_mcp.models` - **THEN** all canonical models (Creature, Spell, Weapon, Armor, MagicItem) are available - **AND** the import path is `from lorekeeper_mcp.models import Creature, Spell` #### Scenario: Models package is independent of API clients - **GIVEN** the `models/` package exists - **WHEN** examining its dependencies - **THEN** it does not import from `api_clients/` - **AND** it can be used by both API clients and parsers --- ### Requirement: Slug Normalization All canonical models SHALL normalize the `slug` field from various source field names (`key`, `slug`, or generated from `name`). #### Scenario: Normalize API key field to slug - **GIVEN** an entity dict with `{"key": "fireball", "name": "Fireball"}` - **WHEN** the entity is validated through a canonical model - **THEN** the model has `slug="fireball"` - **AND** the original `key` field is not required after validation #### Scenario: Generate slug from name when missing - **GIVEN** an entity dict with `{"name": "Magic Missile"}` and no `slug` or `key` - **WHEN** the entity is validated through a canonical model - **THEN** the model generates `slug="magic-missile"` (lowercase, hyphenated) - **AND** special characters like apostrophes are removed #### Scenario: Preserve explicit slug when provided - **GIVEN** an entity dict with both `{"slug": "custom-slug", "name": "Different Name"}` - **WHEN** the entity is validated - **THEN** the model uses `slug="custom-slug"` as provided - **AND** does not override with generated value --- ### Requirement: Description Field Normalization All canonical models SHALL normalize description fields from `description` to `desc`. #### Scenario: Normalize OrcBrew description field - **GIVEN** an OrcBrew entity with `{"description": "A powerful spell."}` - **WHEN** the entity is validated through a canonical model - **THEN** the model has `desc="A powerful spell."` - **AND** works whether source uses `description` or `desc` #### Scenario: Handle missing description - **GIVEN** an entity without a description field - **WHEN** the entity is validated - **THEN** `desc` is set to `None` - **AND** validation does not fail --- ### Requirement: Creature Model (renamed from Monster) The system SHALL use `Creature` as the canonical model name for monsters/creatures, aligning with Open5e v2 terminology. #### Scenario: Create creature from API response - **GIVEN** an Open5e v2 creature response with nested objects - **WHEN** validated through the `Creature` model - **THEN** `size` is extracted as string (from `size.name`) - **AND** `type` is extracted as string (from `type.name`) - **AND** `challenge_rating` is string and `challenge_rating_decimal` is float #### Scenario: Backward compatibility with Monster alias - **GIVEN** existing code using `from lorekeeper_mcp.models import Monster` - **WHEN** the import is executed - **THEN** `Monster` resolves to `Creature` class - **AND** a deprecation warning is logged #### Scenario: Create creature from OrcBrew data - **GIVEN** an OrcBrew creature with `challenge` field (not `challenge_rating`) - **WHEN** validated through the `Creature` model - **THEN** `challenge` is mapped to `challenge_rating` - **AND** `challenge_rating_decimal` is computed from the value --- ### Requirement: Spell Model with Class List Normalization The `Spell` model SHALL normalize the `classes` field to a list of lowercase strings regardless of source format. #### Scenario: Normalize API class objects to strings - **GIVEN** a spell with `classes: [{"index": "wizard", "name": "Wizard"}]` - **WHEN** validated through the `Spell` model - **THEN** `classes` becomes `["wizard"]` - **AND** all class names are lowercase #### Scenario: Handle comma-separated class string - **GIVEN** a spell with `classes: "Wizard, Sorcerer, Warlock"` - **WHEN** validated through the `Spell` model - **THEN** `classes` becomes `["wizard", "sorcerer", "warlock"]` #### Scenario: Preserve empty class list - **GIVEN** a spell with no classes specified - **WHEN** validated - **THEN** `classes` is an empty list `[]` - **AND** validation does not fail --- ### Requirement: Weapon Model with Simplified Damage Type The `Weapon` model SHALL provide a `damage_type_name` property for easy access to the damage type string. #### Scenario: Access damage type as string - **GIVEN** a weapon with `damage_type: {"name": "Slashing", "key": "slashing"}` - **WHEN** accessing `weapon.damage_type_name` - **THEN** returns `"Slashing"` #### Scenario: Store simplified damage type from OrcBrew - **GIVEN** an OrcBrew weapon with `damage-type: "piercing"` - **WHEN** validated through the `Weapon` model - **THEN** `damage_type` stores the string directly - **AND** `damage_type_name` returns `"piercing"` --- ### Requirement: OrcBrew Relaxed Models The system SHALL provide OrcBrew-specific model variants with relaxed field requirements for incomplete data. #### Scenario: Validate OrcBrew spell with missing fields - **GIVEN** an OrcBrew spell with only `name`, `level`, `school`, `description` - **WHEN** validated through `OrcBrewSpell` model - **THEN** validation succeeds - **AND** missing fields (`casting_time`, `range`, `duration`) are `None` #### Scenario: OrcBrew creature without ability scores - **GIVEN** an OrcBrew creature with `name`, `type`, `size`, `challenge` - **WHEN** validated through `OrcBrewCreature` model - **THEN** validation succeeds - **AND** ability scores default to `None` - **AND** `hit_points` and `hit_dice` default to `None` #### Scenario: Convert OrcBrew model to canonical - **GIVEN** an `OrcBrewSpell` instance - **WHEN** calling `to_canonical()` method - **THEN** returns a `Spell` instance with same data - **AND** missing fields remain `None`

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/frap129/lorekeeper-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server