Inspo
Design references from real production websites for coding agents building UI
Construction quantity takeoff and estimates from plan PDFs: scale, measure, count, price and export
Official · Kentucky AI TypeScript Local · stdio DesignData and analytics
70 popularity score ★ 767 ↓ 1.8k/wk
Opens the app, which asks before it adds the server.
claude mcp add opentakeoff -- npx -y opentakeoff-mcp{
"mcpServers": {
"opentakeoff": {
"command": "npx",
"args": [
"-y",
"opentakeoff-mcp"
]
}
}
}{
"mcpServers": {
"opentakeoff": {
"command": "npx",
"args": [
"-y",
"opentakeoff-mcp"
]
}
}
}{
"servers": {
"opentakeoff": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"opentakeoff-mcp"
]
}
}
}[mcp_servers.opentakeoff]
command = "npx"
args = ["-y", "opentakeoff-mcp"]gemini mcp add --scope user opentakeoff npx -- -y opentakeoff-mcpOpens the app, which asks before it adds the server. Then replace each <NAME> in its settings with your value.
git clone https://github.com/Kentucky-ai/opentakeoff.git
cd opentakeoff/web && npm install
cd ../mcp && npm installclaude mcp add opentakeoff -- node --import tsx '<ABSOLUTE_PATH_TO_CLONE>/mcp/server.ts'{
"mcpServers": {
"opentakeoff": {
"command": "node",
"args": [
"--import",
"tsx",
"<ABSOLUTE_PATH_TO_CLONE>/mcp/server.ts"
]
}
}
}{
"mcpServers": {
"opentakeoff": {
"command": "node",
"args": [
"--import",
"tsx",
"<ABSOLUTE_PATH_TO_CLONE>/mcp/server.ts"
]
}
}
}{
"servers": {
"opentakeoff": {
"type": "stdio",
"command": "node",
"args": [
"--import",
"tsx",
"<ABSOLUTE_PATH_TO_CLONE>/mcp/server.ts"
]
}
}
}[mcp_servers.opentakeoff]
command = "node"
args = ["--import", "tsx", "<ABSOLUTE_PATH_TO_CLONE>/mcp/server.ts"]gemini mcp add --scope user opentakeoff node -- --import tsx '<ABSOLUTE_PATH_TO_CLONE>/mcp/server.ts'OPENTAKEOFF_ONE_CLICK |
Optional: Set to 1 to register the gated one_click and detect_rooms tools. |
|---|---|
OPENTAKEOFF_MCP_STAGED_TOOLS |
Optional: Set to 1 to start with the setup tools only and unlock the rest through open_tool_stage. |
OPENTAKEOFF_MCP_TRACE |
Optional: Set to 1 to log one JSON line per tool call to stderr. |
OpenTakeoff is an open-source construction takeoff engine from Kentucky-ai, and opentakeoff-mcp is its local stdio MCP server. An agent opens plan PDFs from disk, reads title blocks and schedules, sets each sheet's scale, and commits area, length, wall-surface and count measurements. It can also sweep a sheet for every copy of a drawn symbol, derive base runs and finish transitions from rooms already measured, and raise RFIs where the drawings contradict themselves. When it's done it can hand off a JSON takeoff, a computed report with a materials buy list, a marked-up PDF planset or a DXF file.
Releases after 0.9.98 also price the work. You enter material and labor rates per condition plus the markups, and the server writes the estimate as an Excel workbook and a bid proposal PDF. It never refuses an unfinished estimate. Instead the reply says which prices are still missing, and every proposal page carries an OpenTakeoff watermark.
The server shares its quantity math and takeoff format with the OpenTakeoff browser canvas. Work an agent commits shows up there as unreviewed 'pencil' proposals, scales it sets land unconfirmed, and a person accepts or rejects them in the canvas. The takeoff lives in memory for the session, so the export tools are how you keep it.
A default build registers 58 tools, and the main README now lists all 58 by stage. What it no longer names are the two gated room tools. The code still holds one_click and detect_rooms but skips registering them while the room-flood engine is re-checked (mcp/src/gate.ts), and setting OPENTAKEOFF_ONE_CLICK=1 brings both back. With OPENTAKEOFF_MCP_STAGED_TOOLS=1 the server starts with just the setup tools plus open_tool_stage, which opens the other tool groups on request. That opener is counted outside the 58.
| Runtime | Node.js 20+ |
|---|---|
| Runs on | macOS, Windows, Linux |
| Also |
|
Rendering (view_sheet, the sheet-image resource, raster tracing of scans) needs the native @napi-rs/canvas package, an optional dependency: npm installs it where a build exists for your platform, and the Claude Desktop .mcpb bundle leaves it out. The source way needs both the web and mcp folders installed, because the engine is imported from web/src/lib through tsx. No account or API key.
one_click and detect_rooms are not registered unless OPENTAKEOFF_ONE_CLICK=1 is set; until then rooms are traced with measure_polygon. Scales set over MCP stay unconfirmed until a person confirms them in the canvas. Scanned sheets with no text layer give sheet_graph, resolve_tag and detect_rooms nothing to read. Joining sheets split at a match line is canvas-only, and import_takeoff drops the app's stitches field, so a stitched takeoff loses its stitches on a round trip. load_plan replaces the session unless merge: true is passed. Staged tools need a client that honours tools/list_changed. The Docker image is built from the repository; no published image is named.
Estimators and preconstruction teams who want an agent to do first-pass flooring and finish takeoffs on local PDF plan sets, then price them into a draft estimate and bid proposal, with a person reviewing the result in the OpenTakeoff canvas before it counts. It runs entirely on your machine with no account, so plans stay local. It is not a fit if you need automatic room detection today (that path is gated), stitched multi-sheet floors over MCP, a proposal without the OpenTakeoff watermark, or a hosted endpoint. Agents working without a human reviewer will leave every measurement and scale unconfirmed.
15 only read, 39 change things, 6 can delete or overwrite. A ? marks an optional parameter.
load_plan changespath: stringmerge?: booleansheet_infosheet: stringset_scale changessheet: stringlabel?: stringupp?: numbercalibrate?: objectuse_detected?: booleanone_click changessheet: stringx: numbery: numbercondition?: stringrole?: enumreturn_verts?: booleansensitivity?: numberlayers?: objectdetect_rooms changessheet: stringcondition?: stringassign_from_schedule?: booleanrole?: enumreturn_verts?: booleanmin_area_sf?: numbersensitivity?: numberlayers?: objectpropose_takeoff changeslabel: stringrationale: stringmeasure_polygon changessheet: stringverts: arraycondition?: stringrole?: enumarc_through?: arraycut_out changesparent_shape_id: stringverts: arraymeasure_line changessheet: stringpts: arraycondition?: stringrise_ft?: numberdrop_ft?: numberarc_through?: arraymeasure_surface changessheet: stringpts: arraycondition: stringheight_ft?: numberarc_through?: arrayplace_count changessheet: stringpoints: arraycondition: stringsymbol_sweep changessheet: stringseed_rect: arraycondition?: stringcommit?: booleancommit_seed?: booleanscope?: enumrotations?: booleanmirror?: booleantolerance_px?: numbervariant_guard?: booleanexclude?: arrayluminance_tolerance?: numbersweep_schedule_row changestag: stringcommit?: booleanrotations?: booleanmirror?: booleantolerance_px?: numbercount_marks changesmarks?: arraycommit?: booleanderive_base changessource_condition: stringcondition: stringopenings?: arrayderive_transitions changescondition_a: stringcondition_b: stringcondition: stringmax_gap_in?: numbermin_run_in?: numbertakeoff_summaryexport_takeoff deletespath?: stringoverwrite?: booleanexport_dxf deletespath: stringsheet?: stringunits?: enumoverwrite?: booleanexport_report deletespath?: stringproject_name?: stringoverwrite?: booleanset_pricing changescondition: stringunit?: enummaterial?: numberlabor?: numbermaterials?: arrayset_estimate_settings changestax_pct?: numberoverhead_pct?: numberprofit_pct?: numberprofit_mode?: enumproposal?: objectclient?: objectestimate_summaryexport_estimate deletespath?: stringproject_name?: stringcompany_name?: stringoverwrite?: booleanexport_proposal deletespath?: stringproject_name?: stringcompany?: objectoverwrite?: booleanimport_takeoff changespath: stringapply_rules changessheet?: stringexport_marked_pdf deletespath?: stringproject_name?: stringoverwrite?: booleanlist_shapessheet?: stringcondition?: stringdelete_shape changesshape_id: stringscope_duplicatessheet?: stringmin_fraction?: numberscope_merge changesshape_a: stringshape_b: stringwinner?: stringrevise_proposal changesproposal_id: stringshapes: arraywithdraw_proposal changesproposal_id: stringsheet_contextsheet: stringregion?: objectmin_len_px?: numbermax_segments?: numberget_sheet_vectorssheet: stringregion?: objectlimit?: numbercursor?: numberedit_shape changesshape_id: stringverts?: arraycondition?: stringrole?: enumlabel?: stringrise_ft?: numberdrop_ft?: numberedit_materials changescondition: stringadd?: arrayremove?: arraypatch?: arrayedit_condition changescondition: stringwaste_pct?: numbermultiplier?: numberheight_ft?: numberrise_ft?: numberdrop_ft?: numberroll_setup?: anypropose_condition_edit changescondition: stringfinish_tag?: stringwaste_pct?: numbermultiplier?: numberheight_ft?: numberrise_ft?: numberdrop_ft?: numberroll_setup?: anyrationale: stringwithdraw_condition_edit changesproposal_id: stringduplicate_condition changescondition: stringlabel: stringsplit_condition changescondition: stringundo_last changesn?: numbersheet_graphresolve_tagtag: stringfind_schedulekind: stringread_sheet_textsheet: stringregion?: objectfind_textsheet: stringq: stringregion?: objectlimit?: numberview_sheetsheet: stringregion?: objectpx?: numberoverlay?: booleangrid?: stringmarks?: objectannotate changessheet: stringtype: enumtext?: stringcondition?: stringat?: arraytarget?: arrayrect?: arrayfrom?: arrayto?: arrayr?: numberlist_annotationssheet?: stringcondition?: stringedit_annotation changesannotation_id: stringtext: stringlink_annotation changesannotation_id: stringcondition: stringmark_verdict changesshape_id?: stringsheet?: stringat?: arraytext?: stringdelete_verdict changesverdict_id: stringcreate_rfi changestitle: stringquestion: stringsheet: stringmarkup_ids?: arraylist_rfisresolve_rfi changesrfi_id: stringanswer: stringdelete_rfi changesrfi_id: stringtakeoff://wikitakeoff://wiki/{page}takeoff://protocoltakeoff://sheetstakeoff://sheet/{page}takeoff://sheet/{page}/texttakeoff://sheet/{page}/image