Skip to content
ContractorKeith
Go back

an mcp server that reads plans like an estimator

4 min read

silt fence is the oldest trap in fence estimating. it has fence right in the name, it shows up on the same civil sheets as my scope, and it belongs to the erosion control contractor, not me. i’ve watched people count it. i may have counted it once myself, a long time ago.

an ai reading a plan set cold will count it every time.

so when we built conmcp, an open-source mcp server that reads commercial construction plans, the playbook it hands the model starts with judgment, not math: what to count, what to exclude, and what to turn into an RFI.

what it is

point any mcp client — Claude Desktop, Claude Code, Cursor, or your own agent — at a folder of plan pdfs, and the model gets eight tools for working a plan set the way an estimator does: index the sheets, search for the ones that matter, read the notes and title blocks, look at the actual drawings, pull the schedules, extract the quantity callouts, and turn all of it into a structured takeoff report.

there is also a fence_takeoff prompt that runs the whole guided workflow from one request. type “run a fence takeoff on project.pdf” and go get coffee.

the server never calls a model

the design decision i care most about: conmcp makes zero LLM calls itself. deterministic work stays deterministic. “450 LF - 6’ HIGH CHAIN LINK FENCE W/ 3-STRAND BARBED WIRE” is text sitting on page 2 of the sample plan, and a pattern matcher pulls it out exactly, every run, with the page number attached.

conmcp quantities output listing page-by-page callouts from the sample plan: 450 LF of 6-foot chain link with barbed wire, 128 LF of 8-foot ornamental aluminum, three gate callouts, a 215 SF concrete pad, and 8-foot on-center post spacing, with unit totals of 578 LF and 215 SF

because here’s the thing about letting a language model transcribe quantities: it gets them right most of the time. most of the time is not a standard you can bid on.

the drawing itself is different. a site plan is a picture, and reading it takes eyes. render_page hands your client’s vision model the sheet as a png: graphic scale, fence lines, gate symbols, title block. the model does the looking. which model does the looking is your call, cloud or fully local, and the server itself never uploads anything.

the rendered site plan, sheet C-101 of the synthetic Oakridge Commerce Park sample: graphic scale, fence lines with quantity callouts, a proposed building, gate callouts, and a title block

37 years of gotchas, as a tool call

get_takeoff_instructions returns the playbook i wish someone had handed me decades ago. silt fence is erosion control, not fence scope. gate operators need power, and the power is by others, so coordinate with the electrical contractor. post spacing comes from the detail sheet, not the site plan. and when the plan says “TYP OF 2” but the gate schedule lists one G-2 row, that is not a judgment call. that’s an RFI before bid day.

the sample takeoff report shows all of it working:

the generated takeoff report for the sample plan: six line items with quantities, confidence scores, and source citations, silt fence excluded, a G-2 gate count mismatch flagged for review, plus assumptions and RFI candidates

six line items. silt fence excluded, with the reason cited to C-601 note 4. the G-2 count mismatch flagged at 70% confidence with a needs-review triangle and both sources named. every line carries a confidence score and the sheet it came from, because a quantity without a source is a guess with good posture.

it is a bidding aid, not a bid. a human checks the flags. that rule lives in the schema itself.

built for sensitive documents

bid documents are other people’s intellectual property, and i treat them that way. the server only touches directories you list in CONMCP_ALLOWED_DIRS, and the default fails closed. every file access lands in an append-only audit log. no network calls, and resource caps against malicious pdfs.

the only plan pdf in the repo is a synthetic 4-sheet sample built by a generator script. contributing rule number one: never commit real plans.

it feeds the rest of the stack

the report JSON matches contractor-bid’s BOM line-item format, and the CSV columns line up with plan markup’s takeoff export. so a conmcp takeoff drops straight into the same pipeline the takeoff suite already runs.

try it

uvx --from git+https://github.com/ContractorKeith/conmcp conmcp sample
uvx --from git+https://github.com/ContractorKeith/conmcp conmcp overview sample_plan.pdf

hook it into Claude Code:

claude mcp add conmcp -e CONMCP_ALLOWED_DIRS=/Users/you/plans -- \
  uvx --from git+https://github.com/ContractorKeith/conmcp conmcp serve

85 tests, CI on python 3.11 through 3.13, MIT license. full docs, client setup, and the takeoff schema are at contractorkeith.github.io/conmcp.

v0.1 is fence-first because fence is what i know. the sitework profile is a starter, and the roadmap has local vision backends and more trades on it. if you estimate a different trade and can tell me what your sheets look like, open an issue. i’d like to know where this breaks.


Share this post on:

Next Post
i needed a better window into my ai agents