Skip to main content
Glama
Construction Project Intelligence MCP Server

Construction Project Intelligence MCP Server

by ConSpen

Construction Project Intelligence MCP Server

CI Python MCP License

An MCP server that gives AI assistants (Claude Desktop, Claude Code, any MCP client) read-only access to live construction project data and the project document library at the same time. Users can ask questions like:

"Which issues are putting the North Campus handover at risk, and what do the latest reports say about them?"

The assistant then pulls the issues from the project platform, scores the schedule risk, finds the inspection reports and minutes that reference those issues, and cites its sources.

Note: This is a sanitised reference implementation. The platform API, the projects and the documents are fictional and run locally. The architecture is modelled on common integration patterns between construction project platforms and enterprise document stores.

Why this exists

On large CAPEX projects, the truth about a project is split across systems:

  • Issues, inspections and schedules live in a project platform.

  • Context (minutes, inspection reports, specifications) lives in a document library.

Project controls teams spend hours stitching the two together every week. An MCP server puts that join behind a small set of well-defined tools. Any AI assistant can use them safely, and the business logic stays testable and reusable.

Related MCP server: Keystone MCP Server

Architecture

flowchart LR
    U[User in Claude Desktop / Claude Code] --> A[AI assistant]
    A -- MCP stdio / HTTP --> S[MCP server<br/>thin tool layer]
    S --> PI[ProjectIntelligence<br/>service layer]
    PI --> PC[PlatformClient<br/>REST, auth, pagination, retries]
    PI --> DS[DocumentStore<br/>metadata + TF-IDF search]
    PC --> API[(Project platform API<br/>mock: FastAPI)]
    DS --> DOCS[(Document library<br/>mock: Markdown + YAML;<br/>prod: Microsoft Graph)]

Layer

Responsibility

File

MCP tools

Tool contracts, input limits, error-to-message mapping, prompt template

src/construction_mcp/server.py

Service

Joins platform and documents, filtering, risk scoring

src/construction_mcp/service.py

Platform connector

Async REST, bearer auth, pagination, retry with backoff on 429/5xx

src/construction_mcp/platform_client.py

Document connector

DocumentStore protocol, local implementation with metadata and ranked search

src/construction_mcp/document_store.py

Mock platform

Deterministic fictional data, auth, pagination, optional rate limiting

mock_platform/app.py

Tools

All tools are annotated readOnlyHint: true. The assistant can investigate, but it cannot change source data.

Tool

What it does

list_projects

Projects with phase, location, planned completion

search_issues

Filter by status, priority, discipline, overdue; sorted by priority then due date

get_issue

One issue, days overdue, and the documents that reference it (falls back to search)

search_documents

Ranked full-text search over minutes, reports and specs, scoped by project

read_document

Full text and metadata of one document

project_risk_brief

Risk score, discipline hotspots, worst overdue issues with evidence, and the scoring method

Prompt: weekly_site_brief(project_id) is a template for a one-page steering-meeting brief.

Example: project_risk_brief("P-200") (real output, abridged)

{
  "open_issues": 18, "overdue_issues": 16, "critical_or_high_overdue": 5,
  "risk_score": 58, "risk_level": "elevated",
  "hotspots": [
    {"discipline": "HVAC", "open_issues": 6, "overdue_issues": 6, "risk_points": 68},
    {"discipline": "Electrical", "open_issues": 5, "overdue_issues": 5, "risk_points": 49}
  ],
  "top_overdue": [
    {"issue": {"id": "ISS-2006", "title": "AHU commissioning sheet incomplete at Level 2 lab wing", "priority": "critical"},
     "days_overdue": 69, "linked_documents": [{"id": "DOC-201", "title": "Commissioning status report, October"}]}
  ],
  "method": "Each open issue scores its priority weight (low 1, medium 2, high 4, critical 8). Overdue issues are doubled ..."
}

Design decisions

  • Thin MCP layer, fat service layer. Business logic is plain Python, tested without MCP. The same service can drive a scheduled report or a dashboard.

  • Explainable scoring. The risk score is a simple, documented formula returned with every brief, so a project manager can challenge it. Priority dominates and lateness only amplifies it, so a stale low-priority snag never outranks a fresh critical one. This is covered by a unit test.

  • Read-only by default. Write actions on site data (closing issues, changing dates) should go through a human approval step, not an LLM tool call.

  • Errors are answers. Platform failures return a clear message to the assistant (e.g. "check CPI_PLATFORM_TOKEN") instead of crashing the session.

  • Swappable connectors. DocumentStore is a protocol. The local implementation can be replaced with Microsoft Graph (SharePoint) or a vector index without touching the tools.

  • Bounded outputs. Tool limits are capped, so a single call cannot flood the model context.

Quickstart

git clone https://github.com/ConSpen/construction-mcp-server
cd construction-mcp-server
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

# 1. start the mock project platform
uvicorn mock_platform.app:app --port 8765

# 2. run the tests (no server needed, uses an in-process transport)
pytest

# 3. try the server interactively
CPI_AS_OF=2026-11-02 mcp dev src/construction_mcp/server.py

Use it from Claude Desktop

Add this to claude_desktop_config.json, using absolute paths:

{
  "mcpServers": {
    "construction-intelligence": {
      "command": "/path/to/construction-mcp-server/.venv/bin/construction-mcp",
      "env": {
        "CPI_PLATFORM_URL": "http://127.0.0.1:8765",
        "CPI_PLATFORM_TOKEN": "demo-token",
        "CPI_DOCUMENTS_PATH": "/path/to/construction-mcp-server/sample_docs",
        "CPI_AS_OF": "2026-11-02"
      }
    }
  }
}

Then ask: "Give me a weekly site brief for P-100."

Configuration

Variable

Default

Purpose

CPI_PLATFORM_URL

http://127.0.0.1:8765

Platform API base URL

CPI_PLATFORM_TOKEN

demo-token

Bearer token

CPI_DOCUMENTS_PATH

sample_docs

Document library root

CPI_AS_OF

today

Pin "today" for demos and reproducible results

CPI_MAX_RETRIES

3

Retries on 429 / 5xx

CPI_MOCK_FLAKY_EVERY

off

Mock only: return 429 on every Nth call to exercise retries

Testing

There are 18 tests across three levels:

  • Connector: pagination, filters, auth errors, retry and give-up behaviour

  • Service: search ranking, project scoping, issue-document linking, risk model invariants

  • MCP: tool registration, read-only annotations, tool round trips, error handling

CI runs ruff and pytest on every push.

Taking this to production

  • Replace LocalDocumentStore with a Microsoft Graph connector (delegated auth, so users only see documents they already have access to).

  • Add caching with short TTLs for list endpoints, plus structured logging of tool calls for audit.

  • Run over streamable HTTP behind the organisation's identity provider (OAuth), not with a shared token.

  • Add an evaluation set of real questions with expected tool calls, and track answer quality over time.

License

MIT. Built with Claude Code.

Available Tools

6 tools
get_issueA
Read-onlyIdempotent

Get one issue with days overdue and the documents that reference it.

NameRequiredDescriptionDefault
issue_idYes

Output Schema

NameRequiredDescription

No output parameters

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, so the safety profile is covered. The description adds that results are enriched with days overdue and referencing documents, but says nothing about behavior on a missing/invalid issue_id or any access constraints.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single sentence with zero filler, front-loading the resource and scope before the enrichment details. Nothing is redundant or padded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so the description needn't enumerate return fields, and annotations cover the safety profile for this simple single-parameter read. Only the issue_id format and not-found behavior are left unaddressed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There is one required parameter (issue_id) with 0% schema description coverage, so the schema gives only the name and type. The description does not clarify whether issue_id is a key, numeric identifier, or composite, leaving the agent to infer format from convention alone.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb+resource ("Get one issue") plus the scope "one" makes it distinguishable from the sibling search_issues, which returns sets. It also previews the enrichment (days overdue, referencing documents), though it never names the sibling it contrasts with.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase "one issue" implies usage when a single known issue is needed, and the singular framing implicitly steers multi-issue queries to search_issues, but no explicit when-to-use or when-not-to-use statement is given.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_projectsB
Read-onlyIdempotent

List all projects with phase, location and planned completion date.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
resultYes

TDQS

B3.1/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, openWorldHint=false and destructiveHint=false, so the safety profile is fully covered without the description. The description adds no further behavioral context such as whether archived or deleted projects are included, result ordering, or result-set size limits.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence with no redundant phrasing; the resource and the returned fields come first. It is close to minimal, though it spends words enumerating output fields that the output schema already documents.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

This is a low-complexity, parameterless read tool with an output schema, so the description does not need to explain return values. It is still silent on scope questions an agent would ask before calling it, such as whether all projects are truly returned or whether the list is paginated/truncated.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so there is nothing for the description to disambiguate and the schema already closes the surface completely. Baseline for a parameterless tool is the correct treatment here.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb and resource ('List all projects') and names the fields surfaced (phase, location, planned completion date), so the agent knows what it gets back. It does not reference any sibling such as project_risk_brief, so the differentiation from adjacent project-related tools is left implicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no explicit statement of when to use this tool versus alternatives like project_risk_brief or search_issues, and no exclusions or prerequisites. Usage is only inferable from the word 'List all', which is the minimum implied guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

project_risk_briefB
Read-onlyIdempotent

Schedule-risk brief for a project: score, discipline hotspots and the worst overdue issues with their supporting documents. Includes the scoring method.

NameRequiredDescriptionDefault
project_idYes

Output Schema

NameRequiredDescription

No output parameters

TDQS

B3.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish read-only, idempotent, closed-world behavior, so the safety bar is low. The description adds real value beyond that by disclosing the response contents (ranked hotspots, worst overdue issues, supporting documents, and the scoring methodology), which tells the agent how to interpret and trust the output.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two compact sentences, front-loaded with the deliverable and its payload. The trailing clause about the scoring method is a small but genuinely useful addition rather than filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return-value detail is partly covered elsewhere, and annotations handle the safety profile. What's missing is scoping the brief itself: no time window for 'overdue', no statement of how many 'worst' issues are returned, and no note on whether an invalid project_id errors or returns an empty brief.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% and the description says nothing about project_id — not its format, nor that it corresponds to an ID returned by list_projects. With a single required parameter left entirely undocumented, the description fails to compensate for the schema gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a concrete deliverable (a schedule-risk brief for a project) and enumerates its contents: score, discipline hotspots, worst overdue issues with supporting documents, and the scoring method. This clearly separates it from the granular siblings like search_issues and get_issue, though it never explicitly states the relationship to those tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied: an agent can infer this is the aggregate-overview tool rather than per-issue lookups, but there is no explicit 'use this when you want X instead of paging through search_issues' guidance. No prerequisites (e.g., that project_id must come from list_projects) or exclusion conditions are stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

read_documentB
Read-onlyIdempotent

Read the full text and metadata of one document by ID.

NameRequiredDescriptionDefault
document_idYes

Output Schema

NameRequiredDescription

No output parameters

TDQS

B3.4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, non-destructive, and closed-world, so the safety profile is covered. The description adds that the payload includes both full text and metadata, but says nothing about behavior for missing or unauthorized IDs, truncation of large documents, or access scope.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence with no filler; the return scope and the lookup key are both stated up front.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need no elaboration, and the tool is a simple single-parameter read. Only the absence of retrieval workflow guidance and error-condition behavior keeps it short of full completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

One parameter with 0% schema description coverage, so the schema only gives the name 'document_id'. The description indicates an ID is required and that it addresses a single document, but does not clarify expected ID format or provenance. Modest value added over the bare schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (read), resource (one document), and scope (full text and metadata, by ID). The 'one document by ID' phrasing implicitly separates it from search_documents, though no sibling is named explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No when-to-use guidance, no prerequisites, and no reference to search_documents as the way to find an ID first. An agent must infer the retrieval workflow on its own.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_documentsB
Read-onlyIdempotent

Full-text search over the project document library (minutes, reports, specs).

NameRequiredDescriptionDefault
limitNo
queryYes
project_idNo

Output Schema

NameRequiredDescription
resultYes

TDQS

B3/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, idempotentHint=true, destructiveHint=false and openWorldHint=false, so the safety profile is fully covered by structured data. The description adds no behavior beyond purpose: nothing about result ranking, truncation, the default limit of 5, or what happens when project_id is omitted (search across all projects?).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single economical sentence with the resource and content types front-loaded and zero filler. It is not padded, but the terseness edges toward under-specification given three undocumented parameters.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return-value explanation is legitimately absent. Still, for a search tool with an optional project filter and a limit parameter, the description should say whether omitting project_id searches all projects — a material behavioral decision left unstated.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the full burden of explaining the three parameters, and it says nothing about any of them. The optional project_id scoping and the limit default of 5 are the semantically important ones, and neither is clarified anywhere.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb plus resource ("Full-text search over the project document library") and even enumerates the content types searched (minutes, reports, specs). It is clearly distinct from read_document, though it does not name that sibling explicitly to state the search-vs-fetch distinction.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

"Full-text search" implies keyword lookup when an identifier is unavailable, which implicitly routes the agent away from read_document. However, there is no explicit when-to-use statement, no mention of how this differs from search_issues, and no note about scoping to a project.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_issuesA
Read-onlyIdempotent

Find issues on a project, sorted by priority then due date.

Filter by status (open, in_progress, closed), priority (low..critical), discipline (e.g. "MEP", "Structural") or overdue_only.

NameRequiredDescriptionDefault
limitNo
statusNo
priorityNo
disciplineNo
project_idYes
overdue_onlyNo

Output Schema

NameRequiredDescription
resultYes

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, destructiveHint=false and openWorldHint=false, so the safety profile is covered without the description. The description adds useful non-obvious behavior — the priority-then-due-date ordering — but omits the truncation behavior implied by the limit parameter (default 20), which is the main behavioral risk for a search tool. It also doesn't say whether results are scoped to a single project only.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the verb, resource, and ordering, with no filler. The filter enumeration is compact and scannable. Minor deduction for a stray trailing newline and for listing filters without grouping by which are mutually exclusive or combinable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return shape needn't be described. However, for a six-parameter search tool the description never explains the limit/pagination contract or the role of the required project_id, leaving an agent unable to reason about result truncation. Adequate but with a real gap for a listing tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the full burden and does meaningful work: it enumerates the status values (open, in_progress, closed), gives the priority range (low..critical), and supplies example discipline values ("MEP", "Structural") that the bare string schema does not convey. It leaves limit and the required project_id entirely unexplained, which keeps it from a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("Find issues on a project") plus the result ordering ("sorted by priority then due date"), which is genuinely differentiating information. It implicitly separates itself from the single-record sibling get_issue, but never names that sibling or search_documents explicitly, so it stops short of full sibling differentiation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The filter list implies the intended use case (narrowing a project's issue set), but there is no explicit when-to-use guidance, no mention of when to prefer get_issue for a known ID, and no statement about what an unfiltered call returns. Usage must be inferred from the parameter enumeration.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 6 tool updatesv0.1.0
    • First observedget_issue
    • First observedlist_projects
    • First observedproject_risk_brief
    • First observedread_document
    • First observedsearch_documents
    • First observedsearch_issues

TDQS

A3.5/5.0

Scored across 6 tools

Disambiguation4/5

Most tools have clearly distinct resource/action boundaries: document search vs. document read, issue search vs. issue retrieval, and project listing vs. risk briefing. The only mild overlap is that project_risk_brief also surfaces overdue issues, which search_issues can also find, but the aggregate scoring and hotspot focus keep it distinct.

Naming Consistency4/5

Five of six tools follow a clear verb_noun pattern: search_documents, read_document, list_projects, search_issues, get_issue. project_risk_brief is a noun phrase that breaks the pattern, but the overall convention remains predictable.

Tool Count5/5

Six tools is well-scoped for a construction project intelligence server focused on projects, documents, issues, and risk. Each tool covers a distinct operation without obvious padding.

Completeness4/5

The surface covers the core read-oriented intelligence workflows: project listing, document search/read, issue search/retrieval, and a project risk brief. Minor gaps remain, such as no dedicated get_project detail operation or issue creation/update, though these may be outside the server's read-only scope.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    F
    maintenance
    Provides AI assistants with read-only access to Secureframe's compliance data, enabling querying of security controls, tests, users, vendors, and more across frameworks like SOC 2 and ISO 27001.
    8
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI assistants to search and manage construction project economic data, including unit price analyses, resource catalogs, budget trees, and price lists, with secure multi-tenant access.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to query live construction project data such as RFIs, submittals, budget variance, daily logs, punch lists, and inspection checklists.
    -