Skip to main content
Glama
plan.md15.4 kB
# Implementation Plan: Basic Facets (Entity, Property, Attribute) **Branch**: `claude/005-basic-facets` | **Date**: 2025-11-01 | **Spec**: [DESIGN_SPECIFICATION.md](../../DESIGN_SPECIFICATION.md) ## Summary Implement MCP tools for adding the three most common IDS facets: Entity (IFC types), Property (property sets), and Attribute (IFC attributes). These facets form the foundation of most IDS specifications and are used in both applicability and requirements sections. ## Technical Context **Language/Version**: Python 3.8+ **Primary Dependencies**: - `fastmcp` - MCP tools and Context - `ifctester` - `ids.Entity`, `ids.Property`, `ids.Attribute` classes - `pydantic` - Parameter validation **Storage**: Session-based via Context **Testing**: pytest with TDD, IfcTester validation **Target Platform**: Cross-platform Python **Project Type**: Single project **Performance Goals**: Facet creation <5ms **Constraints**: - Must support all facet parameters per IDS spec - Must handle applicability vs requirements location - Must support cardinality for requirements **Scale/Scope**: Support 50+ facets per specification ## Constitution Check **Required Checks:** - ✅ IfcTester supports all three facet types - ✅ Facets can be added to applicability/requirements lists - ✅ Cardinality only applies to requirements - ✅ Entity names validated against IFC schema ## Project Structure ```text specs/005-basic-facets/ ├── plan.md ├── research.md # IfcTester facet classes research ├── data-model.md # Facet parameter models └── contracts/ # Tool contracts src/ids_mcp_server/ ├── tools/ │ └── facets.py # Facet management tools └── utils/ └── facet_helpers.py # Common facet operations tests/ ├── unit/tools/ │ └── test_facet_tools.py └── integration/ └── test_facet_workflow.py ``` ## Phase 0: Research **Research Questions:** - How to create Entity facets with predefinedType? - How to create Property facets with all parameters? - How to create Attribute facets? - When does cardinality apply? - How to find and update facets in specification? **Deliverable**: `research.md` with IfcTester facet examples ## Phase 1: Design ### Tool Parameter Models ```python from pydantic import BaseModel, Field, validator from typing import Optional class AddEntityFacetParams(BaseModel): """Parameters for add_entity_facet tool.""" spec_id: str = Field(..., description="Specification identifier") location: str = Field(..., description="'applicability' or 'requirements'") entity_name: str = Field(..., description="IFC entity name (e.g., 'IFCWALL')") predefined_type: Optional[str] = Field(None, description="Predefined type") cardinality: str = Field("required", description="Cardinality (requirements only)") @validator('location') def validate_location(cls, v): if v not in ["applicability", "requirements"]: raise ValueError("location must be 'applicability' or 'requirements'") return v @validator('entity_name') def validate_entity_name(cls, v): """Ensure entity name is uppercase.""" return v.upper() class AddPropertyFacetParams(BaseModel): """Parameters for add_property_facet tool.""" spec_id: str location: str property_name: str = Field(..., description="Property base name") property_set: Optional[str] = Field(None, description="Property set name") data_type: Optional[str] = Field(None, description="IFC data type") property_location: str = Field("any", description="'instance', 'type', or 'any'") cardinality: str = Field("required") @validator('property_location') def validate_property_location(cls, v): if v not in ["instance", "type", "any"]: raise ValueError("property_location must be 'instance', 'type', or 'any'") return v class AddAttributeFacetParams(BaseModel): """Parameters for add_attribute_facet tool.""" spec_id: str location: str attribute_name: str = Field(..., description="Attribute name (e.g., 'Name')") cardinality: str = Field("required") ``` ### Tool Contracts ```python @mcp.tool async def add_entity_facet( spec_id: str, location: str, entity_name: str, ctx: Context, predefined_type: str = None, cardinality: str = "required" ) -> dict: """ Add entity facet to specification. Contract: - MUST create ids.Entity object - MUST find specification by spec_id - MUST append to correct location (applicability/requirements) - MUST uppercase entity_name - MUST ignore cardinality in applicability Returns: { "status": "added", "facet_type": "entity", "spec_id": "S1", "location": "applicability" } """ @mcp.tool async def add_property_facet( spec_id: str, location: str, property_name: str, ctx: Context, property_set: str = None, data_type: str = None, property_location: str = "any", cardinality: str = "required" ) -> dict: """ Add property facet to specification. Contract: - MUST create ids.Property object - MUST support optional property_set - MUST support optional data_type - MUST handle property_location (instance/type/any) - MUST apply cardinality only to requirements """ @mcp.tool async def add_attribute_facet( spec_id: str, location: str, attribute_name: str, ctx: Context, cardinality: str = "required" ) -> dict: """ Add attribute facet to specification. Contract: - MUST create ids.Attribute object - MUST support common attributes (Name, Description, Tag, etc.) """ ``` ## Phase 2: Test-Driven Implementation ### Milestone 1: Entity Facet Tool **RED: Write failing tests** ```python import pytest from fastmcp.client import Client from ifctester import ids @pytest.mark.asyncio async def test_add_entity_facet_to_applicability(): """Test adding entity facet to applicability.""" mcp = create_test_server() async with Client(mcp) as client: await client.call_tool("create_ids", {"title": "Test"}) result = await client.call_tool("add_specification", { "name": "Wall Spec", "ifc_versions": ["IFC4"] }) spec_id = result["spec_id"] # Add entity facet result = await client.call_tool("add_entity_facet", { "spec_id": spec_id, "location": "applicability", "entity_name": "IFCWALL" }) assert result["status"] == "added" assert result["facet_type"] == "entity" # Validate with IfcTester from ids_mcp_server.session.storage import _session_storage session_data = _session_storage.get(client.session_id) spec = session_data.ids_obj.specifications[0] assert len(spec.applicability) == 1 entity = spec.applicability[0] assert isinstance(entity, ids.Entity) assert entity.name == "IFCWALL" @pytest.mark.asyncio async def test_add_entity_facet_with_predefined_type(): """Test entity facet with predefined type.""" mcp = create_test_server() async with Client(mcp) as client: await setup_test_spec(client) result = await client.call_tool("add_entity_facet", { "spec_id": "S1", "location": "applicability", "entity_name": "IFCWALL", "predefined_type": "SOLIDWALL" }) # Validate session_data = _session_storage.get(client.session_id) entity = session_data.ids_obj.specifications[0].applicability[0] assert entity.predefinedType == "SOLIDWALL" @pytest.mark.asyncio async def test_entity_name_uppercased(): """Test that entity names are automatically uppercased.""" mcp = create_test_server() async with Client(mcp) as client: await setup_test_spec(client) await client.call_tool("add_entity_facet", { "spec_id": "S1", "location": "applicability", "entity_name": "ifcwall" # lowercase }) # Should be uppercased session_data = _session_storage.get(client.session_id) entity = session_data.ids_obj.specifications[0].applicability[0] assert entity.name == "IFCWALL" ``` **GREEN: Implementation** 1. Create `tools/facets.py` 2. Implement `add_entity_facet` 3. Use IfcTester `ids.Entity()` 4. Run tests → PASS ### Milestone 2: Property Facet Tool **RED: Write failing tests** ```python @pytest.mark.asyncio async def test_add_property_facet_minimal(): """Test adding property facet with minimal parameters.""" mcp = create_test_server() async with Client(mcp) as client: await setup_test_spec(client) result = await client.call_tool("add_property_facet", { "spec_id": "S1", "location": "requirements", "property_name": "FireRating" }) # Validate session_data = _session_storage.get(client.session_id) prop = session_data.ids_obj.specifications[0].requirements[0] assert isinstance(prop, ids.Property) assert prop.baseName == "FireRating" assert prop.cardinality == "required" @pytest.mark.asyncio async def test_add_property_facet_full(): """Test property facet with all parameters.""" mcp = create_test_server() async with Client(mcp) as client: await setup_test_spec(client) result = await client.call_tool("add_property_facet", { "spec_id": "S1", "location": "requirements", "property_name": "FireRating", "property_set": "Pset_WallCommon", "data_type": "IFCLABEL", "property_location": "type", "cardinality": "optional" }) # Validate session_data = _session_storage.get(client.session_id) prop = session_data.ids_obj.specifications[0].requirements[0] assert prop.baseName == "FireRating" assert prop.propertySet == "Pset_WallCommon" assert prop.dataType == "IFCLABEL" assert prop.cardinality == "optional" @pytest.mark.asyncio async def test_property_facet_exports_correctly(): """Test that property facet exports to valid XML.""" mcp = create_test_server() async with Client(mcp) as client: await setup_test_spec(client) await client.call_tool("add_property_facet", { "spec_id": "S1", "location": "requirements", "property_name": "LoadBearing", "property_set": "Pset_WallCommon", "cardinality": "required" }) result = await client.call_tool("export_ids", {}) # Validate with IfcTester validated_ids = ids.from_string(result["xml"], validate=True) prop = validated_ids.specifications[0].requirements[0] assert prop.baseName == "LoadBearing" assert prop.propertySet == "Pset_WallCommon" ``` **GREEN: Implementation** 1. Implement `add_property_facet` 2. Use IfcTester `ids.Property()` 3. Run tests → PASS ### Milestone 3: Attribute Facet Tool **RED: Write failing tests** ```python @pytest.mark.asyncio async def test_add_attribute_facet(): """Test adding attribute facet.""" mcp = create_test_server() async with Client(mcp) as client: await setup_test_spec(client) result = await client.call_tool("add_attribute_facet", { "spec_id": "S1", "location": "requirements", "attribute_name": "Name", "cardinality": "required" }) # Validate session_data = _session_storage.get(client.session_id) attr = session_data.ids_obj.specifications[0].requirements[0] assert isinstance(attr, ids.Attribute) assert attr.name == "Name" assert attr.cardinality == "required" @pytest.mark.asyncio async def test_common_attributes(): """Test common IFC attributes.""" common_attrs = ["Name", "Description", "Tag", "ObjectType"] mcp = create_test_server() async with Client(mcp) as client: await setup_test_spec(client) for attr_name in common_attrs: await client.call_tool("add_attribute_facet", { "spec_id": "S1", "location": "requirements", "attribute_name": attr_name }) # Verify all added session_data = _session_storage.get(client.session_id) spec = session_data.ids_obj.specifications[0] assert len(spec.requirements) == 4 ``` **GREEN: Implementation** 1. Implement `add_attribute_facet` 2. Use IfcTester `ids.Attribute()` 3. Run tests → PASS ### Milestone 4: Complete Workflow Test ```python @pytest.mark.asyncio async def test_complete_specification_with_facets(): """ Integration test: Create complete specification with all facet types. """ mcp = create_test_server() async with Client(mcp) as client: # Create IDS await client.call_tool("create_ids", { "title": "Complete Test", "author": "test@example.com" }) # Add specification result = await client.call_tool("add_specification", { "name": "Wall Requirements", "ifc_versions": ["IFC4"] }) spec_id = result["spec_id"] # Add entity to applicability await client.call_tool("add_entity_facet", { "spec_id": spec_id, "location": "applicability", "entity_name": "IFCWALL" }) # Add property to requirements await client.call_tool("add_property_facet", { "spec_id": spec_id, "location": "requirements", "property_name": "FireRating", "property_set": "Pset_WallCommon", "cardinality": "required" }) # Add attribute to requirements await client.call_tool("add_attribute_facet", { "spec_id": spec_id, "location": "requirements", "attribute_name": "Name", "cardinality": "required" }) # Export and validate result = await client.call_tool("export_ids", {}) # Critical: Validate with IfcTester validated_ids = ids.from_string(result["xml"], validate=True) spec = validated_ids.specifications[0] assert len(spec.applicability) == 1 assert len(spec.requirements) == 2 assert isinstance(spec.applicability[0], ids.Entity) assert isinstance(spec.requirements[0], ids.Property) assert isinstance(spec.requirements[1], ids.Attribute) ``` ## Phase 3: Validation **Validation Checklist:** - ✅ All three facet types implemented - ✅ Facets can be added to both applicability and requirements - ✅ Cardinality handled correctly - ✅ Exported XML validates against XSD - ✅ Test coverage ≥ 95% ## Success Metrics - ✅ All tools respond in <5ms - ✅ 100% XSD compliance - ✅ Complete workflow test passes - ✅ Test coverage ≥ 95% ## Next Steps After Phase 005: 1. Proceed to **006-advanced-facets** - Implement Classification, Material, PartOf facets 2. These build on the same patterns as basic facets

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/vinnividivicci/ifc-ids-mcp'

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