Skip to main content
Glama

IDS MCP Server

DESIGN_SPECIFICATION.md83.8 kB
# IDS MCP Server - Software Design Specification **Version:** 2.2 **Date:** 2025-11-01 **Status:** Draft --- ## Table of Contents 1. [Executive Summary](#1-executive-summary) 2. [System Overview](#2-system-overview) 3. [IDS Standard Deep Dive](#3-ids-standard-deep-dive) 4. [FastMCP Framework Architecture](#4-fastmcp-framework-architecture) 5. [System Architecture](#5-system-architecture) 6. [MCP Tool Design](#6-mcp-tool-design) 7. [Data Models](#7-data-models) 8. [Validation and Compliance](#8-validation-and-compliance) 9. [Error Handling Strategy](#9-error-handling-strategy) 10. [Testing Strategy](#10-testing-strategy) 11. [Deployment Considerations](#11-deployment-considerations) 12. [Future Enhancements](#12-future-enhancements) --- ## 1. Executive Summary ### 1.1 Purpose This document specifies the design for an MCP (Model Context Protocol) server that enables AI agents to deterministically create buildingSMART IDS (Information Delivery Specification) files that are 100% compliant with the IDS 1.0 standard. ### 1.2 Goals - **100% IDS Schema Compliance**: All generated IDS files must validate against the official XSD schema at `https://standards.buildingsmart.org/IDS/1.0/ids.xsd` - **Deterministic Generation**: Given the same input, the system must produce identical, valid IDS files - **AI-Friendly Interface**: Provide simple, intuitive tools that AI agents can reliably use - **Comprehensive Coverage**: Support all IDS facets, restrictions, and cardinalities defined in the standard ### 1.3 Technology Stack - **Framework**: FastMCP (Python) - https://github.com/jlowin/fastmcp - **IDS Library**: IfcTester (part of IfcOpenShell) - https://docs.ifcopenshell.org/ifctester.html - **Target Standard**: buildingSMART IDS 1.0 - **Schema Source**: https://standards.buildingsmart.org/IDS/1.0/ids.xsd - **Language**: Python 3.8+ - **Key Dependencies**: - `fastmcp` - MCP server framework - `ifctester` - IDS authoring, reading, and validation (official IfcOpenShell library) - `ifcopenshell` - IFC file processing (included with ifctester) - `pydantic` - Data validation and modeling for MCP tool parameters --- ## 2. System Overview ### 2.1 What is IDS? Information Delivery Specification (IDS) is a buildingSMART standard for specifying and checking information requirements from IFC (Industry Foundation Classes) models. It provides: - Computer-interpretable XML format for defining requirements - Automatic compliance checking of IFC models - Standardized approach to model validation - Unambiguous interpretation across different software tools ### 2.2 What is MCP? Model Context Protocol (MCP) is a standardized protocol for connecting AI assistants to external tools and data sources. FastMCP provides a Python framework for building MCP servers with: - `@mcp.tool` decorator for exposing Python functions as tools - `@mcp.resource` decorator for exposing read-only data - Automatic parameter validation and type conversion - Built-in error handling and client integration ### 2.3 System Purpose This MCP server acts as a bridge between AI agents and the complex IDS specification, providing: 1. **Simple Tools**: AI agents call simple MCP tools to build IDS files incrementally 2. **IfcTester Integration**: Leverages the official IfcOpenShell IfcTester library for IDS operations 3. **Validation**: Each operation validates against IDS schema rules using IfcTester 4. **State Management**: Server maintains IDS document state during construction 5. **Export & Import**: Read existing IDS files and export new ones as compliant XML 6. **IFC Validation**: Optional capability to validate IFC models against created IDS files --- ## 3. IDS Standard Deep Dive ### 3.1 IDS File Structure Based on the official specification, an IDS file has this XML structure: ```xml <ids:ids xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://standards.buildingsmart.org/IDS http://standards.buildingsmart.org/IDS/1.0/ids.xsd" xmlns:ids="http://standards.buildingsmart.org/IDS"> <ids:info> <ids:title>Example IDS</ids:title> <ids:version>1.0</ids:version> <ids:author>[email protected]</ids:author> <ids:date>2024-01-06</ids:date> <ids:description>Optional description</ids:description> <ids:copyright>Optional copyright</ids:copyright> <ids:milestone>Optional milestone</ids:milestone> <ids:purpose>Optional purpose</ids:purpose> </ids:info> <ids:specifications> <ids:specification ifcVersion="IFC4X3" name="Specification Name" identifier="S1" minOccurs="0" maxOccurs="unbounded" description="Optional description" instructions="Optional instructions"> <ids:applicability> <!-- Facets that identify relevant model elements --> </ids:applicability> <ids:requirements> <!-- Facets that specify required information --> </ids:requirements> </ids:specification> </ids:specifications> </ids:ids> ``` ### 3.2 IDS Information Model #### 3.2.1 Info Element (Required) Metadata about the IDS file itself: | Element | Required | Type | Description | |---------|----------|------|-------------| | `title` | Yes | string | Human-readable title | | `version` | No | string | Version identifier | | `author` | No | string | Author email/name | | `date` | No | date | Creation/modification date | | `description` | No | string | Detailed description | | `copyright` | No | string | Copyright information | | `milestone` | No | string | Project milestone | | `purpose` | No | string | Purpose of this IDS | #### 3.2.2 Specification Element Each specification has three parts: 1. **Description Fields** - `name` (required): Specification name - `identifier` (optional): Unique identifier - `description` (optional): Why this information is required - `instructions` (optional): How to fulfill requirements - `ifcVersion` (required): One or more IFC versions (IFC2X3, IFC4, IFC4X3, etc.) 2. **Applicability Section** (required) - Identifies subset of model being specified - Uses facets to filter IFC elements - At least one facet required 3. **Requirements Section** (required) - Specifies what information is required - Uses facets with cardinality (required, optional, prohibited) - Can have zero or more facets #### 3.2.3 Cardinality Each specification and requirement facet can specify: - **Required** (default): Information must be present - **Optional**: Information may be present (recommendations) - **Prohibited**: Information must NOT be present Cardinality is specified via: - `minOccurs` and `maxOccurs` attributes on specification - `cardinality` attribute on requirement facets (not available on entity facets in requirements) ### 3.3 The Six Facets Based on the official documentation, IDS supports exactly six facet types: #### 3.3.1 Entity Facet Identifies IFC entity types (e.g., IfcWall, IfcDoor). **Parameters:** - `name` (required): IFC entity class name (e.g., "IFCWALL") - `predefinedType` (optional): Specific type from enum (e.g., "MOVEABLE" for IfcWallTypeEnum) **Usage:** - In Applicability: Filters to specific entity types - In Requirements: Requires specific entity type (cardinality always "required") **Example:** ```xml <ids:entity> <ids:name> <ids:simpleValue>IFCWALL</ids:simpleValue> </ids:name> <ids:predefinedType> <ids:simpleValue>MOVEABLE</ids:simpleValue> </ids:predefinedType> </ids:entity> ``` #### 3.3.2 Attribute Facet IFC attributes like Name, Description, Tag, ObjectType, PredefinedType. **Parameters:** - `name` (required): Attribute name (e.g., "Name", "Description") - `value` (optional): Required value or pattern **Usage:** - In Applicability: Filters by attribute values - In Requirements: Requires specific attribute values with cardinality **Example:** ```xml <ids:attribute cardinality="required"> <ids:name> <ids:simpleValue>Name</ids:simpleValue> </ids:name> <ids:value> <ids:restriction base="xs:string"> <xs:pattern value="EW-.*"/> </ids:restriction> </ids:value> </ids:attribute> ``` #### 3.3.3 Property Facet IFC properties and quantities (property sets). **Parameters:** - `propertySet` (optional): Property set name (e.g., "Pset_WallCommon") - `baseName` (required): Property name (e.g., "FireRating") - `dataType` (optional): IFC data type (uppercase, e.g., "IFCBOOLEAN") - `value` (optional): Required value or pattern - `location` (optional): Where property is attached ("instance", "type", or "any") **Usage:** - Most common facet for requirements - Can specify property set, property name, and value constraints **Example:** ```xml <ids:property cardinality="required" dataType="IFCLABEL"> <ids:propertySet> <ids:simpleValue>Pset_WallCommon</ids:simpleValue> </ids:propertySet> <ids:baseName> <ids:simpleValue>FireRating</ids:simpleValue> </ids:baseName> <ids:value> <ids:restriction base="xs:string"> <xs:enumeration value="REI30"/> <xs:enumeration value="REI60"/> <xs:enumeration value="REI90"/> </ids:restriction> </ids:value> </ids:property> ``` #### 3.3.4 Classification Facet Classification systems (e.g., Uniclass, OmniClass). **Parameters:** - `system` (optional): Classification system URI or name - `value` (required): Classification code or pattern **Usage:** - Requires elements to have specific classifications - Can reference bSDD (buildingSMART Data Dictionary) via URI **Example:** ```xml <ids:classification cardinality="required"> <ids:system> <ids:simpleValue>Uniclass 2015</ids:simpleValue> </ids:system> <ids:value> <ids:simpleValue>EF_25_10_25</ids:simpleValue> </ids:value> </ids:classification> ``` #### 3.3.5 Material Facet Material assignments. **Parameters:** - `value` (required): Material name, category, or URI **Usage:** - Requires specific materials - Can reference material URIs (e.g., from bSDD) **Example:** ```xml <ids:material cardinality="required"> <ids:value> <ids:simpleValue>Concrete</ids:simpleValue> </ids:value> </ids:material> ``` #### 3.3.6 PartOf Facet Relationship requirements (containment, aggregation). **Parameters:** - `relation` (required): Relationship type (e.g., "IFCRELAGGREGATES", "IFCRELCONTAINEDINSPATIALSTRUCTURE") - `entity` (required): Parent entity specification (entity facet) **Usage:** - Requires specific structural relationships - E.g., "Door must be part of a Space" **Example:** ```xml <ids:partOf cardinality="required" relation="IFCRELCONTAINEDINSPATIALSTRUCTURE"> <ids:entity> <ids:name> <ids:simpleValue>IFCSPACE</ids:simpleValue> </ids:name> </ids:entity> </ids:partOf> ``` ### 3.4 Facet Parameters and Restrictions #### 3.4.1 Simple Values The simplest form - exact string match: ```xml <ids:simpleValue>IFCWALL</ids:simpleValue> ``` #### 3.4.2 Restrictions (Complex Values) For advanced constraints using XML Schema facets: **Enumeration** (list of valid values): ```xml <ids:restriction base="xs:string"> <xs:enumeration value="REI30"/> <xs:enumeration value="REI60"/> <xs:enumeration value="REI90"/> </ids:restriction> ``` **Pattern** (regex matching): ```xml <ids:restriction base="xs:string"> <xs:pattern value="EW-[0-9]{3}"/> </ids:restriction> ``` **Bounds** (numeric ranges): ```xml <ids:restriction base="xs:double"> <xs:minInclusive value="2.4"/> <xs:maxInclusive value="3.0"/> </ids:restriction> ``` **Length constraints**: ```xml <ids:restriction base="xs:string"> <xs:length value="10"/> </ids:restriction> ``` ```xml <ids:restriction base="xs:string"> <xs:minLength value="5"/> <xs:maxLength value="50"/> </ids:restriction> ``` #### 3.4.3 Base Types Restrictions must specify a base XML Schema type: - `xs:string` - Text values - `xs:integer` - Integer numbers - `xs:double` - Decimal numbers - `xs:boolean` - True/false - `xs:date` - Date values - `xs:time` - Time values - `xs:dateTime` - Date and time ### 3.5 IFC Versions Supported IFC versions (case-insensitive, space-separated list): - `IFC2X3` - `IFC4` - `IFC4X3` (also known as IFC4.3) Multiple versions can be specified: ```xml <ids:specification ifcVersion="IFC2X3 IFC4 IFC4X3" ...> ``` --- ## 4. FastMCP Framework Architecture ### 4.1 Core Concepts FastMCP provides three building blocks: 1. **Tools** (`@mcp.tool`): Execute actions, perform computations 2. **Resources** (`@mcp.resource`): Expose read-only data via URIs 3. **Prompts** (`@mcp.prompt`): Reusable LLM interaction templates For this IDS MCP server, we'll primarily use **Tools**. ### 4.2 Tool Design Pattern ```python from fastmcp import FastMCP mcp = FastMCP("IDS MCP Server") @mcp.tool def create_specification( name: str, ifc_versions: list[str], description: str = None ) -> dict: """ Create a new IDS specification. Args: name: Specification name ifc_versions: List of IFC versions (e.g., ["IFC4", "IFC4X3"]) description: Optional description Returns: Dictionary with specification ID and status """ # Implementation return {"spec_id": "S1", "status": "created"} ``` Key features: - **Type hints** drive automatic parameter validation - **Docstrings** provide tool descriptions to AI agents - **Return types** are automatically serialized - **Errors** can be raised as `ToolError` for user-friendly messages ### 4.3 Error Handling FastMCP provides two error modes: 1. **ToolError**: User-facing errors (validation failures, business logic errors) ```python from fastmcp import ToolError raise ToolError("Invalid IFC version: IFC99") ``` 2. **Masked Errors**: Internal errors masked in production to prevent information leakage ```python mcp = FastMCP("IDS Server", mask_error_details=True) ``` ### 4.4 State Management FastMCP doesn't provide built-in state management. For this server, we'll need: - **Session-based state**: Store IDS document being built - **Context parameter**: Access to MCP context for logging - **In-memory storage**: During development - **Persistent storage**: For production (optional) ### 4.5 Testing Pattern FastMCP provides built-in testing via in-memory client: ```python from fastmcp import FastMCP from fastmcp.client import Client async with Client(mcp) as client: result = await client.call_tool("create_ids", { "title": "Test IDS", "author": "test@example.com" }) ``` ### 4.5 IfcTester Integration IfcTester is the official Python library from IfcOpenShell for working with IDS files. It provides a complete, tested, and maintained implementation of the IDS specification. #### 4.5.1 Why IfcTester? Using IfcTester provides significant advantages: 1. **Official Implementation**: Developed and maintained by the IfcOpenShell team 2. **Battle-Tested**: Used in production by the BIM industry 3. **Complete Coverage**: Supports all IDS 1.0 features 4. **Built-in Validation**: Validates against official XSD schema 5. **IFC Integration**: Can validate IFC models against IDS (bonus feature) 6. **Community Support**: Active development and issue tracking #### 4.5.2 IfcTester Core API **Creating IDS Documents:** ```python from ifctester import ids # Create new IDS my_ids = ids.Ids( title="My IDS", author="[email protected]", description="Project requirements" ) # Add specification spec = ids.Specification( name="Wall Fire Rating", ifcVersion=["IFC4"], description="All walls must have fire rating" ) # Add applicability (what to check) spec.applicability.append(ids.Entity(name="IFCWALL")) # Add requirements (what to require) requirement = ids.Property( baseName="FireRating", propertySet="Pset_WallCommon", cardinality="required" ) spec.requirements.append(requirement) # Add to IDS my_ids.specifications.append(spec) # Save to file my_ids.to_xml("output.ids") ``` **Reading IDS Documents:** ```python # Load from file my_ids = ids.open("existing.ids") # Or from string xml_string = "..." my_ids = ids.from_string(xml_string) # Access specifications for spec in my_ids.specifications: print(spec.name) print(spec.applicability) print(spec.requirements) ``` **IDS Facet Classes:** IfcTester provides classes for all six facet types: - `ids.Entity(name, predefinedType=None)` - `ids.Property(baseName, propertySet=None, value=None, dataType=None, cardinality="required")` - `ids.Attribute(name, value=None, cardinality="required")` - `ids.Classification(value, system=None, cardinality="required")` - `ids.Material(value, cardinality="required")` - `ids.PartOf(entity, relation, cardinality="required")` **Restrictions:** IfcTester supports IDS restrictions using the `Restriction` class or simple values: ```python # Simple value ids.Entity(name="IFCWALL") # Enumeration restriction restriction = ids.Restriction( base="xs:string", options={"enumeration": ["REI30", "REI60", "REI90"]} ) ids.Property(baseName="FireRating", value=restriction) # Pattern restriction restriction = ids.Restriction( base="xs:string", options={"pattern": "EW-[0-9]{3}"} ) # Bounds restriction restriction = ids.Restriction( base="xs:double", options={"minInclusive": 2.4, "maxInclusive": 3.0} ) ``` #### 4.5.3 Validation Capabilities ```python import ifcopenshell # Validate IFC model against IDS ifc_file = ifcopenshell.open("model.ifc") my_ids.validate(ifc_file) # Check validation results for spec in my_ids.specifications: print(f"Specification: {spec.name}") print(f"Status: {spec.status}") print(f"Passed: {len(spec.passed_entities)}") print(f"Failed: {len(spec.failed_entities)}") # Generate reports from ifctester import reporter # Console report reporter.Console(my_ids).report() # HTML report html_reporter = reporter.Html(my_ids) html_reporter.report() html_reporter.to_file("report.html") # JSON report json_reporter = reporter.Json(my_ids) json_reporter.to_file("report.json") ``` #### 4.5.4 Integration Strategy Our MCP server will: 1. **Wrap IfcTester**: Use IfcTester as the underlying IDS engine 2. **Simplify Interface**: Provide simple MCP tools that map to IfcTester API 3. **Maintain State**: Keep `ids.Ids` object in session for incremental building 4. **Validate Early**: Use IfcTester's validation at each step 5. **Extend Functionality**: Add AI-friendly abstractions on top of IfcTester --- ## 5. System Architecture ### 5.1 Architectural Overview ``` ┌─────────────────────────────────────────────────────────────┐ │ AI Agent │ │ (Claude, GPT, etc.) │ └────────────────────────┬────────────────────────────────────┘ │ │ MCP Protocol │ ┌────────────────────────▼────────────────────────────────────┐ │ FastMCP Server │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ MCP Tools Layer │ │ │ │ - create_ids() - load_ids() │ │ │ │ - add_specification() │ │ │ │ - add_entity_facet() │ │ │ │ - add_property_facet() │ │ │ │ - add_attribute_facet() │ │ │ │ - add_restriction() │ │ │ │ - export_ids() - validate_ids() │ │ │ │ - validate_ifc_model() (bonus) │ │ │ └───────────────────────┬───────────────────────────────┘ │ │ │ │ │ ┌───────────────────────▼───────────────────────────────┐ │ │ │ Business Logic Layer │ │ │ │ - IDSSessionManager (manages sessions) │ │ │ │ - IDSBuilder (orchestrates IDS creation) │ │ │ │ - FacetFactory (creates IfcTester facets) │ │ │ │ - RestrictionFactory (creates restrictions) │ │ │ │ - ValidationService (validates operations) │ │ │ └───────────────────────┬───────────────────────────────┘ │ │ │ │ │ ┌───────────────────────▼───────────────────────────────┐ │ │ │ IfcTester Integration Layer │ │ │ │ │ │ │ │ Session State: ids.Ids object │ │ │ │ │ │ │ │ IfcTester Classes: │ │ │ │ - ids.Ids (root document) │ │ │ │ - ids.Specification │ │ │ │ - ids.Entity, ids.Property, ids.Attribute │ │ │ │ - ids.Classification, ids.Material, ids.PartOf │ │ │ │ - ids.Restriction (value constraints) │ │ │ │ │ │ │ │ IfcTester Operations: │ │ │ │ - ids.open() / ids.from_string() │ │ │ │ - to_xml() / to_string() │ │ │ │ - validate(ifc_file) │ │ │ └────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ │ XML I/O │ ┌────────────────────────▼────────────────────────────────────┐ │ IDS XML File │ │ (100% compliant with IDS 1.0 XSD) │ │ Created/validated by IfcTester │ └─────────────────────────────────────────────────────────────┘ ``` ### 5.2 Layer Responsibilities #### 5.2.1 MCP Tools Layer - Exposes simple, atomic operations to AI agents - Validates input parameters using Pydantic (for MCP interface only) - Maintains conversation context and session IDs - Delegates to business logic layer - Returns user-friendly responses - Handles errors and provides clear feedback #### 5.2.2 Business Logic Layer - Orchestrates IDS document construction using IfcTester - Manages session lifecycle and state - Translates MCP tool calls to IfcTester operations - Creates IfcTester facets and restrictions via factories - Validates business constraints before delegating to IfcTester - Coordinates complex multi-step operations #### 5.2.3 IfcTester Integration Layer - **Core Responsibility**: Direct interaction with IfcTester library - Maintains `ids.Ids` object as session state - Creates and manages `ids.Specification` objects - Instantiates IfcTester facet objects (Entity, Property, etc.) - Builds `ids.Restriction` objects for value constraints - Handles XML serialization via `to_xml()` and `to_string()` - Handles XML deserialization via `ids.open()` and `ids.from_string()` - Performs XSD validation (built into IfcTester) - Optional: Validates IFC models against IDS specifications **Key Point**: This layer directly uses IfcTester classes without wrapping them in custom models. The `ids.Ids` object IS our data model. ### 5.3 State Management Architecture (Session-Based with FastMCP Context) **Decision**: Use **Session-Based** state management with **FastMCP Context** for automatic session handling. #### 5.3.1 FastMCP Context Overview FastMCP provides a `Context` object with powerful capabilities: - **Automatic Session Management**: `ctx.session_id` automatically identifies client sessions - **Logging**: `ctx.info()`, `ctx.debug()`, `ctx.warning()`, `ctx.error()` - **Progress Reporting**: `ctx.report_progress()` for long operations - **Request Metadata**: `ctx.request_id`, `ctx.client_id` - **Server Access**: `ctx.server` for accessing FastMCP instance - **Request-Scoped State**: `ctx.set_state()` / `ctx.get_state()` for within-request data **Critical Understanding**: Context state is **request-scoped**, not session-scoped. For session-level persistence (our IDS documents across multiple tool calls), we need a global store keyed by `session_id`. #### 5.3.2 Implementation Strategy **Two-Level State Management:** 1. **Session-Level (Persistent)**: Global dictionary for IDS documents, keyed by `ctx.session_id` 2. **Request-Level (Transient)**: Context state for within-request data **Advantages:** - ✅ Automatic session ID management via FastMCP - ✅ No manual session tracking required - ✅ Built-in logging and progress reporting - ✅ Natural conversation flow - ✅ Clean separation of concerns **Trade-offs:** - ⚠️ Requires cleanup of abandoned sessions - ⚠️ In-memory storage (consider persistence for production) #### 5.3.3 Implementation with FastMCP Context ```python from typing import Dict from ifctester import ids from fastmcp import Context # Session-level storage: persists across requests within same session _session_storage: Dict[str, ids.Ids] = {} async def get_or_create_session(ctx: Context) -> ids.Ids: """ Get existing IDS for this session or create new one. Uses ctx.session_id for automatic session identification. """ session_id = ctx.session_id if session_id not in _session_storage: await ctx.info(f"Creating new IDS session: {session_id}") _session_storage[session_id] = ids.Ids(title="Untitled") else: await ctx.debug(f"Retrieved existing session: {session_id}") return _session_storage[session_id] async def create_session_from_file(ctx: Context, filepath: str) -> ids.Ids: """Load IDS from file and store in session.""" session_id = ctx.session_id await ctx.info(f"Loading IDS from {filepath}") _session_storage[session_id] = ids.open(filepath) return _session_storage[session_id] async def create_session_from_string(ctx: Context, xml_string: str) -> ids.Ids: """Load IDS from XML string and store in session.""" session_id = ctx.session_id await ctx.info("Loading IDS from XML string") _session_storage[session_id] = ids.from_string(xml_string) return _session_storage[session_id] def cleanup_session(session_id: str) -> None: """Remove session data.""" if session_id in _session_storage: del _session_storage[session_id] ``` #### 5.3.4 Tool Implementation Example ```python from fastmcp import FastMCP, Context, ToolError mcp = FastMCP("IDS MCP Server") @mcp.tool async def create_ids( title: str, ctx: Context, # Automatically injected by FastMCP author: str = None, version: str = None, description: str = None ) -> dict: """ Create a new IDS document for this session. Session is automatically tracked by FastMCP. No need to pass session_id parameter! """ try: await ctx.info(f"Creating IDS: {title}") # Create new IDS ids_obj = ids.Ids( title=title, author=author, version=version, description=description ) # Store in session session_id = ctx.session_id _session_storage[session_id] = ids_obj await ctx.info(f"IDS created successfully for session {session_id}") return { "status": "created", "session_id": session_id, "title": title } except Exception as e: await ctx.error(f"Failed to create IDS: {str(e)}") raise ToolError(f"Failed to create IDS: {str(e)}") @mcp.tool async def add_specification( name: str, ifc_versions: list[str], ctx: Context, identifier: str = None, description: str = None ) -> dict: """ Add specification to the session's IDS document. Automatically uses the current session - no session_id parameter needed! """ try: # Get IDS from current session ids_obj = await get_or_create_session(ctx) await ctx.info(f"Adding specification: {name}") # Create specification spec = ids.Specification( name=name, ifcVersion=ifc_versions, identifier=identifier, description=description ) # Add to IDS ids_obj.specifications.append(spec) await ctx.info(f"Specification added: {name}") return { "status": "added", "spec_id": identifier or spec.name, "ifc_versions": ifc_versions } except Exception as e: await ctx.error(f"Failed to add specification: {str(e)}") raise ToolError(f"Failed to add specification: {str(e)}") ``` #### 5.3.5 Session Cleanup Strategy **Development:** - Manual cleanup via dedicated tool - Sessions persist until server restart **Production:** ```python import asyncio from datetime import datetime, timedelta # Track last access time _session_last_access: Dict[str, datetime] = {} async def session_cleanup_task(): """Background task to clean up abandoned sessions.""" while True: await asyncio.sleep(3600) # Run every hour cutoff = datetime.now() - timedelta(hours=24) abandoned = [ sid for sid, last_access in _session_last_access.items() if last_access < cutoff ] for session_id in abandoned: cleanup_session(session_id) del _session_last_access[session_id] # Start cleanup task on server startup asyncio.create_task(session_cleanup_task()) ``` #### 5.3.6 Context Features We'll Use 1. **Session ID**: `ctx.session_id` - Automatic session tracking 2. **Logging**: `ctx.info()`, `ctx.debug()`, `ctx.warning()`, `ctx.error()` 3. **Progress**: `ctx.report_progress()` - For long operations like validation 4. **Request ID**: `ctx.request_id` - For debugging and tracing 5. **Server Access**: `ctx.server` - Access to MCP server instance if needed #### 5.3.7 Key Benefits ✅ **No Manual Session IDs**: FastMCP handles session identification ✅ **Cleaner API**: Tools don't need `session_id` parameters ✅ **Built-in Logging**: Consistent logging via Context ✅ **Progress Reporting**: Real-time feedback for long operations ✅ **Request Tracing**: Every request has unique ID for debugging --- ## 6. MCP Tool Design ### 6.1 Tool Categorization Tools are grouped by functionality: 1. **Document Management**: Create, load, configure, export IDS files 2. **Specification Management**: Add, update specifications 3. **Facet Management**: Add facets to applicability/requirements 4. **Restriction Management**: Configure value constraints 5. **Validation**: Validate IDS document and IFC models 6. **Introspection**: Query document structure ### 6.2 Core Tools Specification **Note**: All tools use `ctx: Context` for automatic session management. No `session_id` parameter needed! #### 6.2.1 Document Management Tools **create_ids** ```python @mcp.tool async def create_ids( title: str, ctx: Context, author: str = None, version: str = None, date: str = None, description: str = None, copyright: str = None, milestone: str = None, purpose: str = None ) -> dict: """ Create a new IDS document for this session. Session is automatically tracked by FastMCP - no session_id parameter needed! Args: title: Document title (required) ctx: FastMCP Context (auto-injected) author: Author email or name version: Version string date: Date in YYYY-MM-DD format description: Document description copyright: Copyright notice milestone: Project milestone purpose: Purpose of this IDS Returns: { "status": "created", "session_id": "auto-generated-by-fastmcp", "title": "..." } Implementation: Creates ids.Ids object and stores in session via ctx.session_id """ ``` **export_ids** ```python @mcp.tool async def export_ids( ctx: Context, output_path: str = None, validate: bool = True ) -> dict: """ Export IDS document to XML file using IfcTester. Uses current session automatically - no session_id parameter needed! Args: ctx: FastMCP Context (auto-injected) output_path: File path (optional, returns XML string if not provided) validate: Whether to validate against XSD (default: True) Returns: { "status": "exported", "xml": "...", # If no output_path "file_path": "...", # If output_path provided "validation_result": {"valid": true, "errors": []} } Implementation: Uses ids.Ids.to_xml(filepath) or ids.Ids.to_string() Gets IDS from session via ctx.session_id """ ``` **load_ids** ```python @mcp.tool async def load_ids( source: str, ctx: Context, source_type: str = "file" ) -> dict: """ Load an existing IDS file into the current session. Replaces any existing IDS in this session. Args: source: File path or XML string ctx: FastMCP Context (auto-injected) source_type: "file" or "string" Returns: { "status": "loaded", "session_id": "auto-generated-by-fastmcp", "title": "...", "specification_count": 3, "specifications": [...] } Implementation: Uses ids.open(filepath) or ids.from_string(xml) Stores in session via ctx.session_id """ ``` **get_ids_info** ```python @mcp.tool async def get_ids_info(ctx: Context) -> dict: """ Get current session's IDS document structure. Uses current session automatically - no session_id parameter needed! Args: ctx: FastMCP Context (auto-injected) Returns: { "title": "...", "author": "...", "specification_count": 3, "specifications": [ { "identifier": "S1", "name": "Wall Fire Rating", "ifc_versions": ["IFC4"], "applicability_facets": 1, "requirement_facets": 2 } ] } Implementation: Gets IDS from session via ctx.session_id """ ``` #### 6.2.2 Specification Management Tools **add_specification** ```python @mcp.tool async def add_specification( name: str, ifc_versions: list[str], ctx: Context, identifier: str = None, description: str = None, instructions: str = None, min_occurs: int = 0, max_occurs: int | str = "unbounded" ) -> dict: """ Add a specification to the current session's IDS document. Args: name: Specification name ifc_versions: List of IFC versions (e.g., ["IFC4", "IFC4X3"]) ctx: FastMCP Context (auto-injected) identifier: Optional unique identifier description: Why this information is required instructions: How to fulfill requirements min_occurs: Minimum occurrences (0 = optional) max_occurs: Maximum occurrences (int or "unbounded") Returns: { "status": "added", "spec_id": "S1", "ifc_versions": ["IFC4"] } Implementation: Gets IDS from ctx.session_id, creates ids.Specification, appends to specifications """ ``` #### 6.2.3 Facet Management Tools **Note**: All facet tools follow the same pattern: - Use `ctx: Context` for automatic session access - Require `spec_id` to identify which specification to modify - `location` parameter specifies "applicability" or "requirements" - Return facet details including generated `facet_id` **add_entity_facet** ```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 an entity facet to a specification. Args: spec_id: Specification identifier location: "applicability" or "requirements" entity_name: IFC entity name (e.g., "IFCWALL") ctx: FastMCP Context (auto-injected) predefined_type: Optional predefined type cardinality: "required", "optional", or "prohibited" (requirements only) Returns: { "status": "added", "facet_type": "entity", "spec_id": "S1" } Implementation: Creates ids.Entity and appends to spec.applicability or spec.requirements """ ``` **add_property_facet** ```python @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 a property facet to a specification. Args: spec_id: Specification identifier location: "applicability" or "requirements" property_name: Property name (e.g., "FireRating") ctx: FastMCP Context (auto-injected) property_set: Property set name (e.g., "Pset_WallCommon") data_type: IFC data type (e.g., "IFCLABEL") property_location: "instance", "type", or "any" cardinality: "required", "optional", or "prohibited" Returns: { "status": "added", "facet_type": "property", "spec_id": "S1" } Implementation: Creates ids.Property and appends to spec.applicability or spec.requirements """ ``` **Other Facet Tools** (follow same pattern): - `add_attribute_facet(spec_id, location, attribute_name, ctx, cardinality="required")` - `add_classification_facet(spec_id, location, classification_value, ctx, classification_system=None, cardinality="required")` - `add_material_facet(spec_id, location, material_value, ctx, cardinality="required")` - `add_partof_facet(spec_id, location, relation, parent_entity, ctx, parent_predefined_type=None, cardinality="required")` All create corresponding IfcTester facet objects and append to the appropriate specification section. #### 6.2.4 Restriction Management Tools **Note**: Restriction tools work with facets directly using IfcTester's `ids.Restriction` class. **Pattern for all restriction tools**: - Take `spec_id` and `parameter_name` (which facet parameter to constrain) - Use `ctx: Context` for session access - Create `ids.Restriction` object with appropriate options - Apply to facet in current session's IDS **Available restriction tools**: **add_enumeration_restriction** ```python @mcp.tool async def add_enumeration_restriction( spec_id: str, parameter_name: str, # e.g., "value", "name" base_type: str, # "xs:string", "xs:integer", etc. values: list[str], ctx: Context ) -> dict: """ Add enumeration restriction (list of valid values). Example: FireRating must be one of ["REI30", "REI60", "REI90"] Implementation: Creates ids.Restriction(base=base_type, options={"enumeration": values}) """ ``` **add_pattern_restriction** ```python @mcp.tool async def add_pattern_restriction( spec_id: str, parameter_name: str, base_type: str, pattern: str, # Regex pattern ctx: Context ) -> dict: """ Add pattern restriction (regex matching). Example: Name must match pattern "EW-[0-9]{3}" Implementation: Creates ids.Restriction(base=base_type, options={"pattern": pattern}) """ ``` **Other Restriction Tools**: - `add_bounds_restriction(spec_id, parameter_name, base_type, ctx, min_inclusive=None, max_inclusive=None, ...)` - `add_length_restriction(spec_id, parameter_name, base_type, ctx, length=None, min_length=None, max_length=None)` All create `ids.Restriction` objects with appropriate options dict. #### 6.2.5 Validation Tools **validate_ids** ```python @mcp.tool async def validate_ids(ctx: Context) -> dict: """ Validate current session's IDS document. Uses IfcTester's built-in validation by attempting to serialize to XML. Args: ctx: FastMCP Context (auto-injected) Returns: { "valid": true, "errors": [], "specifications_count": 3 "warnings": [] } """ ``` **validate_ifc_model** (Bonus Feature) ```python @mcp.tool async def validate_ifc_model( ifc_file_path: str, ctx: Context, report_format: str = "json" # "console", "json", "html" ) -> dict: """ Validate an IFC model against the current session's IDS specifications. This bonus feature leverages IfcTester's IFC validation capabilities. Args: ifc_file_path: Path to IFC file ctx: FastMCP Context (auto-injected) report_format: "console", "json", or "html" Returns: { "status": "validation_complete", "report": "..." # If format is json "html": "..." # If format is html } Implementation: Uses ifcopenshell.open() and ids_obj.validate(ifc_file) Then reporter.Json/Html/Console to generate reports """ ``` ### 6.3 Tool Design Principles 1. **Automatic Session Management**: All tools use `ctx: Context` - no manual session_id tracking 2. **Async by Default**: All tools are async to leverage Context's async methods 3. **Atomic Operations**: Each tool does one thing well 4. **Progressive Construction**: Build IDS incrementally 5. **Immediate Validation**: Validate at each step via IfcTester 6. **Clear Errors**: Provide actionable error messages using `ctx.error()` and `ToolError` 7. **Idempotency**: Same input = same output 8. **Logging**: Use `ctx.info()`, `ctx.debug()` for transparency 9. **Stateful Session**: Maintain document state across calls via `ctx.session_id` --- ## 7. Data Models ### 7.1 Using IfcTester Models Directly **Key Design Decision**: Instead of creating custom Pydantic models for IDS, we directly use IfcTester's native classes. **Rationale:** - IfcTester already provides complete, validated IDS models - Avoids duplication and potential inconsistencies - Leverages battle-tested implementation - Simplifies maintenance and updates - Direct mapping to IDS XML structure **What Pydantic Is Used For:** - MCP tool parameter validation only - Input/output DTOs for tool interfaces - NOT for IDS document representation ### 7.2 IfcTester Model Reference #### 7.2.1 Root Document: `ids.Ids` The main container for an IDS document. ```python from ifctester import ids # Create new IDS my_ids = ids.Ids( title="Project Requirements", author="[email protected]", version="1.0", description="BIM requirements for Project X", copyright="© 2025 Company", date="2025-01-15", purpose="Design stage requirements", milestone="LOD 300" ) # Properties my_ids.info # Dict with metadata my_ids.specifications # List of Specification objects my_ids.filepath # Source file path (if loaded) # Methods my_ids.to_xml("output.ids") # Export to file xml_string = my_ids.to_string() # Export to string my_ids.validate(ifc_file) # Validate IFC model ``` #### 7.2.2 Specification: `ids.Specification` Individual requirement specifications within an IDS. ```python # Create specification spec = ids.Specification( name="Wall Fire Rating", ifcVersion=["IFC4", "IFC4X3"], identifier="WFR-001", description="All load-bearing walls must have fire rating", instructions="Ensure Pset_WallCommon.FireRating is populated", minOccurs=1, maxOccurs="unbounded" ) # Properties spec.applicability # List of facet objects spec.requirements # List of facet objects spec.status # Validation status (after validate()) spec.applicable_entities # IFC entities that match (after validate()) spec.passed_entities # Entities that pass (after validate()) spec.failed_entities # Entities that fail (after validate()) # Methods spec.validate(ifc_file) # Validate against IFC spec.asdict() # Convert to dictionary ``` #### 7.2.3 Facet Classes **Entity Facet:** ```python entity = ids.Entity( name="IFCWALL", predefinedType="SOLIDWALL" # Optional ) ``` **Property Facet:** ```python prop = ids.Property( baseName="FireRating", propertySet="Pset_WallCommon", dataType="IfcLabel", # Optional value="REI60", # Optional cardinality="required", # For requirements uri="https://identifier.buildingsmart.org/uri/..." # Optional ) ``` **Attribute Facet:** ```python attr = ids.Attribute( name="Name", value="EW-.*", # Optional pattern/value cardinality="required" ) ``` **Classification Facet:** ```python classification = ids.Classification( value="EF_25_10_25", system="Uniclass 2015", # Optional cardinality="required" ) ``` **Material Facet:** ```python material = ids.Material( value="Concrete", cardinality="required" ) ``` **PartOf Facet:** ```python # Create parent entity reference parent = ids.Entity(name="IFCSPACE") # Create partOf facet part_of = ids.PartOf( entity=parent, relation="IFCRELCONTAINEDINSPATIALSTRUCTURE", cardinality="required" ) ``` #### 7.2.4 Restriction Class: `ids.Restriction` For complex value constraints: ```python # Enumeration restriction restriction = ids.Restriction( base="xs:string", options={ "enumeration": ["REI30", "REI60", "REI90"] } ) # Pattern restriction restriction = ids.Restriction( base="xs:string", options={ "pattern": "EW-[0-9]{3}" } ) # Bounds restriction restriction = ids.Restriction( base="xs:double", options={ "minInclusive": 2.4, "maxInclusive": 3.0 } ) # Length restriction restriction = ids.Restriction( base="xs:string", options={ "minLength": 5, "maxLength": 50 } ) # Use in facet prop = ids.Property( baseName="Height", value=restriction ) ``` ### 7.3 MCP Tool Parameter Models (Pydantic) While we use IfcTester for IDS models, we still use Pydantic for MCP tool parameters: ```python from pydantic import BaseModel, Field from typing import List, Optional class CreateIDSParams(BaseModel): """Parameters for create_ids tool.""" title: str author: Optional[str] = None version: Optional[str] = None date: Optional[str] = None description: Optional[str] = None copyright: Optional[str] = None milestone: Optional[str] = None purpose: Optional[str] = None class AddSpecificationParams(BaseModel): """Parameters for add_specification tool.""" session_id: str name: str ifc_versions: List[str] identifier: Optional[str] = None description: Optional[str] = None instructions: Optional[str] = None min_occurs: int = 0 max_occurs: int | str = "unbounded" class AddPropertyFacetParams(BaseModel): """Parameters for add_property_facet tool.""" session_id: str spec_id: str location: str # "applicability" or "requirements" property_name: str property_set: Optional[str] = None data_type: Optional[str] = None cardinality: str = "required" # ... more parameter models for each tool ``` ### 7.4 Helper Functions for Model Conversion ```python def create_ids_from_params(params: CreateIDSParams) -> ids.Ids: """Convert MCP parameters to IfcTester Ids object.""" return ids.Ids( title=params.title, author=params.author, version=params.version, date=params.date, description=params.description, copyright=params.copyright, milestone=params.milestone, purpose=params.purpose ) def create_specification_from_params(params: AddSpecificationParams) -> ids.Specification: """Convert MCP parameters to IfcTester Specification.""" return ids.Specification( name=params.name, ifcVersion=params.ifc_versions, identifier=params.identifier, description=params.description, instructions=params.instructions, minOccurs=params.min_occurs, maxOccurs=params.max_occurs ) def create_property_facet_from_params(params: AddPropertyFacetParams) -> ids.Property: """Convert MCP parameters to IfcTester Property facet.""" return ids.Property( baseName=params.property_name, propertySet=params.property_set, dataType=params.data_type, cardinality=params.cardinality if params.location == "requirements" else None ) ``` --- ## 8. Validation and Compliance ### 8.1 Simplified Validation with IfcTester **Key Advantage**: IfcTester handles most validation automatically, significantly simplifying our implementation. ### 8.2 Multi-Level Validation Strategy #### Level 1: MCP Tool Parameter Validation (Pydantic) - Validates MCP tool parameters only - Catches type errors, missing required fields - Ensures valid input before creating IfcTester objects ```python from pydantic import BaseModel, validator class AddSpecificationParams(BaseModel): session_id: str name: str ifc_versions: list[str] @validator('ifc_versions') def validate_ifc_versions(cls, v): """Validate IFC versions.""" valid = {"IFC2X3", "IFC4", "IFC4X3"} for version in v: if version.upper() not in valid: raise ValueError( f"Invalid IFC version: {version}. " f"Valid versions: {', '.join(valid)}" ) return [version.upper() for version in v] ``` #### Level 2: IfcTester Internal Validation - **Built-in to IfcTester**: Automatically validates IDS structure - Enforces IDS specification rules - Validates facet relationships - Ensures compliance during object construction ```python from ifctester import ids # IfcTester validates automatically try: spec = ids.Specification( name="Test", ifcVersion=["INVALID"] # IfcTester will raise error ) except Exception as e: # Handle IfcTester validation errors print(f"Validation failed: {e}") ``` #### Level 3: XSD Schema Validation (IfcTester Built-in) - **Handled by IfcTester**: Uses built-in XSD validation - Can be explicitly invoked with `validate=True` parameter - Ensures 100% compliance with official schema ```python from ifctester import ids # Load with validation try: my_ids = ids.open("file.ids", validate=True) except ids.IdsXmlValidationError as e: print(f"XSD validation failed: {e.xml_error}") # Or validate explicitly using ids.get_schema() schema = ids.get_schema() # IfcTester uses this internally ``` #### Level 4: Business Logic Validation (Our Layer) Lightweight validation for AI-agent-specific concerns: ```python class ValidationService: """Lightweight business logic validation.""" def validate_session_exists(self, session_id: str) -> None: """Ensure session exists.""" if session_id not in _sessions: raise ToolError(f"Session not found: {session_id}") def validate_specification_exists( self, ids_obj: ids.Ids, spec_id: str ) -> ids.Specification: """Find and return specification by ID.""" for spec in ids_obj.specifications: if getattr(spec, 'identifier', None) == spec_id: return spec raise ToolError(f"Specification not found: {spec_id}") def validate_can_export(self, ids_obj: ids.Ids) -> None: """Check if IDS is ready for export.""" if not ids_obj.specifications: raise ToolError("Cannot export: IDS has no specifications") for spec in ids_obj.specifications: if not spec.applicability: raise ToolError( f"Specification '{spec.name}' has no applicability facets" ) ``` ### 8.3 Validation Checkpoints Validation occurs at these points: 1. **Tool Input** (Pydantic): Validate MCP tool parameters 2. **Object Creation** (IfcTester): IfcTester validates during instantiation 3. **Pre-Export** (Business Logic): Check IDS completeness 4. **Export** (IfcTester XSD): Validate against schema when calling `to_xml()` 5. **On-Demand** (`validate_ids()` tool): Explicit validation check ### 8.4 Validation Tool Implementation ```python @mcp.tool def validate_ids(session_id: str) -> dict: """ Validate IDS document. Uses IfcTester's built-in validation. """ try: # Validate session if session_id not in _sessions: raise ToolError(f"Session not found: {session_id}") ids_obj = _sessions[session_id] # Check basic completeness if not ids_obj.specifications: return { "valid": False, "errors": ["IDS has no specifications"] } # Validate by attempting to serialize try: xml_string = ids_obj.to_string() # If this succeeds, IDS is valid return { "valid": True, "errors": [], "specifications_count": len(ids_obj.specifications) } except Exception as e: return { "valid": False, "errors": [str(e)] } except ToolError as e: raise except Exception as e: raise ToolError(f"Validation error: {str(e)}") ``` ### 8.5 IFC Model Validation (Bonus Feature) IfcTester can validate IFC models against IDS specifications: ```python @mcp.tool def validate_ifc_model( session_id: str, ifc_file_path: str, report_format: str = "json" # "console", "json", "html" ) -> dict: """ Validate an IFC model against the IDS specifications. This is a bonus feature leveraging IfcTester's validation capabilities. """ import ifcopenshell from ifctester import reporter try: # Get IDS ids_obj = _sessions[session_id] # Load IFC file ifc_file = ifcopenshell.open(ifc_file_path) # Validate ids_obj.validate(ifc_file) # Generate report if report_format == "console": reporter.Console(ids_obj).report() return {"status": "validation_complete"} elif report_format == "json": json_reporter = reporter.Json(ids_obj) json_reporter.report() return { "status": "validation_complete", "report": json_reporter.to_string() } elif report_format == "html": html_reporter = reporter.Html(ids_obj) html_reporter.report() return { "status": "validation_complete", "html": html_reporter.to_string() } except FileNotFoundError: raise ToolError(f"IFC file not found: {ifc_file_path}") except Exception as e: raise ToolError(f"IFC validation error: {str(e)}") ``` ### 8.6 Error Reporting Errors are reported with clear, actionable messages: ```python from pydantic import BaseModel from typing import List, Optional class ValidationResult(BaseModel): """Validation result from validate_ids tool.""" valid: bool errors: List[str] warnings: List[str] = [] specifications_count: Optional[int] = None ``` --- ## 9. Error Handling Strategy ### 9.1 Error Categories 1. **User Input Errors** (ToolError) - Invalid parameters - Missing required fields - Business rule violations 2. **System Errors** (masked in production) - XML generation failures - Schema loading errors - Internal bugs 3. **Validation Errors** (ToolError) - Schema validation failures - Constraint violations ### 9.2 Error Handling Pattern ```python from fastmcp import ToolError @mcp.tool def add_specification( session_id: str, name: str, ifc_versions: list[str], **kwargs ) -> dict: """Add specification.""" try: # Validate session if session_id not in _sessions: raise ToolError(f"Invalid session ID: {session_id}") # Validate IFC versions valid_versions = {"IFC2X3", "IFC4", "IFC4X3"} for version in ifc_versions: if version.upper() not in valid_versions: raise ToolError( f"Invalid IFC version: {version}. " f"Valid versions: {', '.join(valid_versions)}" ) # Create specification spec = Specification( name=name, ifc_versions=[v.upper() for v in ifc_versions], **kwargs ) # Add to document doc = _sessions[session_id] spec_id = doc.add_specification(spec) return { "status": "added", "spec_id": spec_id, "ifc_versions": spec.ifc_versions } except ToolError: # Re-raise ToolErrors as-is raise except ValueError as e: # Convert validation errors to ToolError raise ToolError(f"Validation error: {str(e)}") except Exception as e: # Log internal errors logger.exception("Internal error in add_specification") # Mask in production if mcp.config.mask_error_details: raise ToolError("An internal error occurred") else: raise ToolError(f"Internal error: {str(e)}") ``` ### 9.3 Logging Strategy ```python import logging # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger("ids_mcp_server") # Log all tool calls logger.info(f"Tool called: add_specification(session_id={session_id}, name={name})") # Log validation failures logger.warning(f"Validation failed: {errors}") # Log errors logger.error(f"Error in tool execution: {str(e)}") logger.exception("Full stack trace") ``` --- ## 10. Testing Strategy - Test-Driven Development (TDD) ### 10.1 TDD Methodology **This project uses Test-Driven Development (TDD) exclusively.** All features must be developed following the Red-Green-Refactor cycle. #### 10.1.1 TDD Workflow ``` ┌─────────────────────────────────────────────────────────────┐ │ TDD Cycle │ │ │ │ 1. RED: Write a failing test │ │ ├─ Define expected behavior │ │ ├─ Write test that exercises new functionality │ │ └─ Run test: MUST FAIL (if it passes, test is wrong) │ │ │ │ 2. GREEN: Make the test pass │ │ ├─ Write minimal implementation │ │ ├─ Run test: MUST PASS │ │ └─ If fails: FIX IMPLEMENTATION (not the test!) │ │ │ │ 3. REFACTOR: Improve code quality │ │ ├─ Clean up implementation │ │ ├─ Remove duplication │ │ └─ All tests still pass │ │ │ │ 4. REPEAT: For next feature │ └─────────────────────────────────────────────────────────────┘ ``` #### 10.1.2 TDD Rules 1. **Write tests FIRST** - No production code without a failing test 2. **Tests must fail initially** - Proves test is testing something 3. **Fix implementation, not tests** - If tests fail after implementation, fix the code 4. **One test at a time** - Focus on one behavior per test 5. **Validate with IfcTester** - Use IfcTester API to ensure correctness ### 10.2 IfcTester-Based Validation **Critical**: All tests must validate using IfcTester API to ensure: - MCP input is correctly transformed to IfcTester objects - IfcTester objects are correctly configured - Generated IDS XML is valid and correct #### 10.2.1 Three-Level Validation ```python from ifctester import ids from fastmcp import FastMCP from fastmcp.client import Client import pytest async def test_create_specification_with_property(): """ Test creating a specification with property requirement. Validates at 3 levels: 1. MCP tool input → IfcTester object creation 2. IfcTester object structure 3. Generated IDS XML output """ # LEVEL 1: MCP Input → IfcTester mcp = create_ids_mcp_server() async with Client(mcp) as client: # Create IDS result = await client.call_tool("create_ids", { "title": "Test IDS" }) # Add specification result = await client.call_tool("add_specification", { "name": "Wall Fire Rating", "ifc_versions": ["IFC4"] }) spec_id = result["spec_id"] # Add property facet result = await client.call_tool("add_property_facet", { "spec_id": spec_id, "location": "requirements", "property_name": "FireRating", "property_set": "Pset_WallCommon", "cardinality": "required" }) # LEVEL 2: Validate IfcTester Object Structure # Access the internal IfcTester object from session ids_obj = _session_storage[client.session_id] # Verify ids.Ids object assert isinstance(ids_obj, ids.Ids) assert ids_obj.info["title"] == "Test IDS" # Verify ids.Specification object assert len(ids_obj.specifications) == 1 spec = ids_obj.specifications[0] assert isinstance(spec, ids.Specification) assert spec.name == "Wall Fire Rating" assert spec.ifcVersion == ["IFC4"] # Verify ids.Property facet assert len(spec.requirements) == 1 prop = spec.requirements[0] assert isinstance(prop, ids.Property) assert prop.baseName == "FireRating" assert prop.propertySet == "Pset_WallCommon" assert prop.cardinality == "required" # LEVEL 3: Validate Generated XML xml_string = ids_obj.to_string() # Validate against XSD schema schema = ids.get_schema() # IfcTester's built-in validation validated_ids = ids.from_string(xml_string, validate=True) assert validated_ids is not None # Verify XML contains expected elements assert "<ids:title>Test IDS</ids:title>" in xml_string assert "<ids:name>Wall Fire Rating</ids:name>" in xml_string assert 'ifcVersion="IFC4"' in xml_string or 'ifcVersion="IFC4 "' in xml_string assert "<ids:baseName>" in xml_string assert "FireRating" in xml_string ``` ### 10.3 Test Categories and Examples #### 10.3.1 Unit Tests - IfcTester Object Creation Test that MCP tools correctly create IfcTester objects. **Example: Entity Facet Creation** ```python import pytest from ifctester import ids def test_entity_facet_creation(): """ RED: Write test first - entity facet with predefined type. Test MUST fail initially (no implementation yet). """ # Create entity facet using our factory from facet_factory import create_entity_facet # This will fail initially - function doesn't exist yet facet = create_entity_facet( entity_name="IFCWALL", predefined_type="SOLIDWALL" ) # Validate using IfcTester API assert isinstance(facet, ids.Entity) assert facet.name == "IFCWALL" assert facet.predefinedType == "SOLIDWALL" # GREEN: Now implement create_entity_facet() to make test pass # Implementation in facet_factory.py: def create_entity_facet(entity_name: str, predefined_type: str = None) -> ids.Entity: return ids.Entity( name=entity_name, predefinedType=predefined_type ) ``` **Example: Restriction Creation** ```python def test_enumeration_restriction(): """ Test creating enumeration restriction for property values. """ from restriction_factory import create_enumeration_restriction # Create restriction restriction = create_enumeration_restriction( base_type="xs:string", values=["REI30", "REI60", "REI90"] ) # Validate using IfcTester API assert isinstance(restriction, ids.Restriction) assert restriction.base == "xs:string" assert "enumeration" in restriction.options assert restriction.options["enumeration"] == ["REI30", "REI60", "REI90"] ``` #### 10.3.2 Component Tests - Business Logic Test that business logic correctly orchestrates IfcTester objects. ```python def test_add_specification_to_session(): """ Test adding specification to session storage. Validates session management and IfcTester integration. """ from session_manager import add_specification_to_session from ifctester import ids # Setup session with empty IDS session_id = "test-session" _session_storage[session_id] = ids.Ids(title="Test") # Add specification spec_id = add_specification_to_session( session_id=session_id, name="Wall Requirements", ifc_versions=["IFC4", "IFC4X3"] ) # Validate using IfcTester API ids_obj = _session_storage[session_id] assert len(ids_obj.specifications) == 1 spec = ids_obj.specifications[0] assert isinstance(spec, ids.Specification) assert spec.name == "Wall Requirements" assert set(spec.ifcVersion) == {"IFC4", "IFC4X3"} ``` #### 10.3.3 Integration Tests - Full MCP Workflows Test complete workflows from MCP tool calls to valid IDS files. ```python @pytest.mark.asyncio async def test_complete_ids_creation_workflow(): """ Integration test: Create complete IDS file with all facet types. This test validates: 1. MCP tool calls work correctly 2. IfcTester objects are created properly 3. Final IDS XML is valid """ mcp = create_ids_mcp_server() async with Client(mcp) as client: # 1. Create IDS document result = await client.call_tool("create_ids", { "title": "Project Requirements", "author": "test@example.com", "description": "Complete test of all features" }) assert result["status"] == "created" # 2. Add specification result = await client.call_tool("add_specification", { "name": "Wall Fire Rating", "ifc_versions": ["IFC4"] }) spec_id = result["spec_id"] # 3. Add entity facet to applicability await client.call_tool("add_entity_facet", { "spec_id": spec_id, "location": "applicability", "entity_name": "IFCWALL" }) # 4. Add property facet to requirements await client.call_tool("add_property_facet", { "spec_id": spec_id, "location": "requirements", "property_name": "FireRating", "property_set": "Pset_WallCommon", "cardinality": "required" }) # 5. Add enumeration restriction await client.call_tool("add_enumeration_restriction", { "spec_id": spec_id, "parameter_name": "value", "base_type": "xs:string", "values": ["REI30", "REI60", "REI90"] }) # 6. Export and validate result = await client.call_tool("export_ids") # Validate result assert result["status"] == "exported" assert "xml" in result xml_string = result["xml"] # CRITICAL: Validate using IfcTester # This ensures the generated XML is truly valid validated_ids = ids.from_string(xml_string, validate=True) # Verify structure using IfcTester API assert validated_ids.info["title"] == "Project Requirements" assert len(validated_ids.specifications) == 1 spec = validated_ids.specifications[0] assert spec.name == "Wall Fire Rating" assert len(spec.applicability) == 1 assert isinstance(spec.applicability[0], ids.Entity) assert len(spec.requirements) == 1 assert isinstance(spec.requirements[0], ids.Property) ``` #### 10.3.4 Validation Tests - Against buildingSMART Examples Test that we can load and validate official IDS examples. ```python def test_load_buildingsmart_example(): """ Test loading official buildingSMART IDS examples. Ensures our implementation can handle real-world IDS files. """ # Load example from buildingSMART repository example_path = "tests/examples/buildingsmart/wall-fire-rating.ids" # Use IfcTester to load and validate ids_obj = ids.open(example_path, validate=True) # Verify it loaded correctly assert ids_obj is not None assert len(ids_obj.specifications) > 0 # Re-export and validate round-trip xml_string = ids_obj.to_string() reloaded = ids.from_string(xml_string, validate=True) # Verify round-trip integrity assert reloaded.info["title"] == ids_obj.info["title"] assert len(reloaded.specifications) == len(ids_obj.specifications) ``` ### 10.4 Test Organization ``` tests/ ├── unit/ │ ├── test_facet_creation.py # Test individual facet creation │ ├── test_restriction_creation.py # Test restriction objects │ └── test_ids_creation.py # Test IDS object creation ├── component/ │ ├── test_session_manager.py # Test session management │ ├── test_specification_builder.py # Test specification building │ └── test_validation_service.py # Test validation logic ├── integration/ │ ├── test_mcp_tools.py # Test all MCP tools │ ├── test_complete_workflows.py # Test end-to-end scenarios │ └── test_context_integration.py # Test FastMCP Context usage ├── validation/ │ ├── test_buildingsmart_examples.py # Test official examples │ ├── test_xsd_compliance.py # Test XSD validation │ └── test_round_trip.py # Test load→modify→save └── fixtures/ ├── example_ids_files/ # Sample IDS files └── expected_outputs/ # Expected XML outputs ``` ### 10.5 Test Coverage Requirements **Mandatory Coverage Targets:** | Test Type | Coverage | Validation Method | |-----------|----------|-------------------| | Unit Tests | 95%+ | IfcTester API assertions | | Component Tests | 100% of business logic | IfcTester object validation | | Integration Tests | All MCP tools | XML validation via IfcTester | | Validation Tests | All official examples | XSD schema validation | **Coverage Enforcement:** ```bash # Run tests with coverage pytest --cov=src --cov-report=html --cov-fail-under=95 # All tests must validate using IfcTester pytest -v --strict-markers ``` ### 10.6 TDD Example: Implementing a New Tool **Scenario**: Add support for Material facets **Step 1: RED - Write Failing Test** ```python @pytest.mark.asyncio async def test_add_material_facet(): """Test adding material facet to specification.""" mcp = create_ids_mcp_server() async with Client(mcp) as client: # Setup await client.call_tool("create_ids", {"title": "Test"}) result = await client.call_tool("add_specification", { "name": "Concrete Walls", "ifc_versions": ["IFC4"] }) spec_id = result["spec_id"] # Add material facet - THIS WILL FAIL (tool doesn't exist yet) result = await client.call_tool("add_material_facet", { "spec_id": spec_id, "location": "requirements", "material_value": "Concrete", "cardinality": "required" }) # Validate using IfcTester ids_obj = _session_storage[client.session_id] spec = ids_obj.specifications[0] assert len(spec.requirements) == 1 material = spec.requirements[0] assert isinstance(material, ids.Material) assert material.value == "Concrete" assert material.cardinality == "required" # Run test: pytest -v test_material_facet.py # Result: FAIL - "add_material_facet" tool not found # ✅ Test fails as expected! ``` **Step 2: GREEN - Implement to Pass** ```python @mcp.tool async def add_material_facet( spec_id: str, location: str, material_value: str, ctx: Context, cardinality: str = "required" ) -> dict: """Add material facet to specification.""" # Get session ids_obj = await get_or_create_session(ctx) # Find specification spec = find_specification(ids_obj, spec_id) # Create IfcTester Material facet material = ids.Material( value=material_value, cardinality=cardinality if location == "requirements" else None ) # Add to appropriate section if location == "applicability": spec.applicability.append(material) else: spec.requirements.append(material) await ctx.info(f"Added material facet: {material_value}") return { "status": "added", "facet_type": "material", "spec_id": spec_id } # Run test: pytest -v test_material_facet.py # Result: PASS # ✅ Implementation works! ``` **Step 3: REFACTOR - If Needed** ```python # Extract common facet-adding logic def add_facet_to_spec(spec, facet, location): """Common logic for adding facets.""" if location == "applicability": spec.applicability.append(facet) else: spec.requirements.append(facet) ``` ### 10.7 Continuous Testing **Pre-commit Hooks:** ```bash #!/bin/bash # .git/hooks/pre-commit # Run all tests pytest tests/ -v # Check coverage pytest --cov=src --cov-fail-under=95 # Validate against IfcTester pytest tests/validation/ -v # If any fail, prevent commit if [ $? -ne 0 ]; then echo "Tests failed! Fix before committing." exit 1 fi ``` **CI/CD Pipeline:** ```yaml # .github/workflows/test.yml name: TDD Validation on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install dependencies run: | pip install fastmcp ifctester pytest pytest-cov - name: Run TDD tests run: | pytest tests/ -v --cov=src --cov-fail-under=95 - name: Validate with IfcTester run: | pytest tests/validation/ -v --strict-markers ``` ### 10.8 Key Testing Principles 1. **Always write tests FIRST** - No exceptions 2. **Tests must fail initially** - Proves they test something 3. **Fix implementation, not tests** - Tests define correct behavior 4. **Validate with IfcTester API** - Ensures real correctness 5. **Test at all levels** - MCP input → IfcTester objects → XML output 6. **Use official examples** - Validate against buildingSMART test suite 7. **Maintain high coverage** - 95%+ for confidence 8. **Run tests frequently** - On every change 9. **Automate validation** - Pre-commit hooks and CI/CD --- ## 11. Deployment Considerations ### 11.1 Development Environment **Local Development:** ```bash # Install dependencies pip install fastmcp lxml pydantic # Run server python -m ids_mcp_server ``` **Claude Desktop Integration:** ```json { "mcpServers": { "ids-mcp": { "command": "python", "args": ["-m", "ids_mcp_server"], "env": { "IDS_LOG_LEVEL": "INFO" } } } } ``` ### 11.2 Production Deployment **Configuration:** ```python mcp = FastMCP( "IDS MCP Server", mask_error_details=True, # Hide internal errors rate_limit="100/hour" # Rate limiting ) ``` **Environment Variables:** ```bash IDS_LOG_LEVEL=WARNING IDS_XSD_CACHE_DIR=/var/cache/ids-mcp IDS_SESSION_TIMEOUT=3600 ``` ### 11.3 Performance Considerations 1. **XSD Caching**: Cache downloaded XSD schema locally 2. **Session Cleanup**: Implement timeout-based session cleanup 3. **Memory Management**: Limit session count, document size 4. **XML Generation**: Stream large documents instead of in-memory ### 11.4 Security Considerations 1. **Input Validation**: All inputs validated via Pydantic 2. **Output Sanitization**: XML special characters escaped 3. **Rate Limiting**: Prevent abuse 4. **Error Masking**: No sensitive information in errors 5. **Session Isolation**: Each session independent --- ## 12. Future Enhancements ### 12.1 Phase 2 Features 1. **IDS Import**: Parse existing IDS files 2. **Template Library**: Pre-built specification templates 3. **Validation Reports**: Detailed HTML/PDF reports 4. **Batch Operations**: Create multiple specifications at once ### 12.2 Phase 3 Features 1. **IFC Validation**: Validate IFC models against IDS 2. **Interactive Builder**: Web UI for visual IDS building 3. **Version Control**: Track IDS file changes over time 4. **Collaboration**: Multi-user editing ### 12.3 Integration Opportunities 1. **BIM Software**: Plugins for Revit, ArchiCAD, etc. 2. **Model Checkers**: Integration with Solibri, ifctester 3. **bSDD Integration**: Automatic property/classification lookup 4. **CI/CD Pipelines**: Automated IFC validation in build pipelines --- ## 13. Implementation Roadmap ### Phase 1: Core Functionality (MVP) **Week 1-2: Foundation** - [ ] Set up project structure - [ ] Implement Pydantic data models - [ ] Create FastMCP server skeleton - [ ] Implement session management **Week 3-4: Basic Tools** - [ ] Document management tools (create, export) - [ ] Specification management tools - [ ] Entity facet support - [ ] Property facet support - [ ] Simple value restrictions **Week 5-6: Advanced Features** - [ ] All facet types (attribute, classification, material, partOf) - [ ] All restriction types (enumeration, pattern, bounds, length) - [ ] XML generation with lxml - [ ] XSD validation **Week 7-8: Testing & Documentation** - [ ] Unit tests (90% coverage) - [ ] Integration tests - [ ] Validate against buildingSMART examples - [ ] User documentation ### Phase 2: Enhancement & Polish **Week 9-10: Advanced Validation** - [ ] Comprehensive business logic validation - [ ] Detailed error messages - [ ] Validation reports **Week 11-12: Production Readiness** - [ ] Performance optimization - [ ] Security hardening - [ ] Deployment documentation - [ ] Production configuration --- ## 14. Appendix ### 14.1 IFC Version Reference | Version | Year | Full Name | Schema URL | |---------|------|-----------|------------| | IFC2X3 | 2006 | IFC 2x3 TC1 | https://standards.buildingsmart.org/IFC/RELEASE/IFC2x3/TC1/HTML/ | | IFC4 | 2013 | IFC 4.0.2.1 | https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/ | | IFC4X3 | 2024 | IFC 4.3.2.0 | https://standards.buildingsmart.org/IFC/RELEASE/IFC4_3/ | ### 14.2 Common IFC Entities - IFCWALL - IFCDOOR - IFCWINDOW - IFCSLAB - IFCBEAM - IFCCOLUMN - IFCSPACE - IFCBUILDING - IFCBUILDINGSTOREY - IFCSITE ### 14.3 Common Property Sets - Pset_WallCommon - Pset_DoorCommon - Pset_WindowCommon - Pset_SpaceCommon - Qto_WallBaseQuantities - Qto_DoorBaseQuantities ### 14.4 Common IFC Data Types - IFCLABEL - IFCTEXT - IFCBOOLEAN - IFCINTEGER - IFCREAL - IFCLENGTHMEASURE - IFCAREAMEASURE - IFCVOLUMEMEASURE ### 14.5 References 1. **buildingSMART IDS GitHub**: https://github.com/buildingSMART/IDS 2. **IDS 1.0 Standard**: https://www.buildingsmart.org/standards/bsi-standards/information-delivery-specification-ids/ 3. **IDS XSD Schema**: https://standards.buildingsmart.org/IDS/1.0/ids.xsd 4. **IDS Documentation**: https://github.com/buildingSMART/IDS/tree/development/Documentation 5. **IfcTester Documentation**: https://docs.ifcopenshell.org/ifctester.html 6. **IfcOpenShell**: https://ifcopenshell.org/ 7. **IfcOpenShell GitHub**: https://github.com/IfcOpenShell/IfcOpenShell 8. **FastMCP Framework**: https://github.com/jlowin/fastmcp 9. **FastMCP Documentation**: https://gofastmcp.com/ --- ## Document History | Version | Date | Author | Changes | |---------|------|--------|---------| | 1.0 | 2025-11-01 | IDS MCP Design Team | Initial design specification | | 2.0 | 2025-11-01 | IDS MCP Design Team | **Major Update**: Integrated IfcTester library for IDS operations. Replaced custom XML generation layer with IfcTester. Simplified architecture by using IfcTester's native models. Added IDS import capability (load_ids tool). Added IFC model validation bonus feature. Simplified validation strategy leveraging IfcTester's built-in validation. | | 2.1 | 2025-11-01 | IDS MCP Design Team | **Context Integration**: Implemented FastMCP Context for automatic session management. Removed manual session_id parameters from all tools. Updated section 5.3 with comprehensive Context implementation guide. All tools now async and use `ctx: Context` for automatic session tracking, logging, and progress reporting. Cleaner API with automatic session handling. Added session cleanup strategy. Updated tool signatures throughout section 6. | | 2.2 | 2025-11-01 | IDS MCP Design Team | **TDD Methodology**: Complete rewrite of Section 10 (Testing Strategy) to enforce Test-Driven Development. Added mandatory TDD workflow (Red-Green-Refactor). Introduced three-level validation using IfcTester API (MCP input → IfcTester objects → XML output). Added comprehensive test examples for all test categories. Included full TDD implementation example. Added test organization structure, coverage requirements (95%+), pre-commit hooks, and CI/CD pipeline configuration. All tests must validate using IfcTester API to ensure correctness at every level. | --- **END OF DOCUMENT**

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