Revit vs Opentakeoff

A side-by-side look at the Revit and Opentakeoff MCP servers: transport, auth, tools, quality signals, and the config to install either one in Claude, Cursor, Windsurf or VS Code.

Revit logoRevitArchitecture & Design · Remote HTTP/SSERevit model query + APS Design Automation runner.51GoodAuth: not declaredOpentakeoff logoOpentakeoffArchitecture & Design · Local subprocess (stdio)Construction plan takeoff on stdio — load a plan PDF, read the title block, adopt the drawing scale (never applied silently), one-click trace rooms, and export real-world quantities (square footage, linear feet, counts) with provenance receipts. Install via NPM: npx -y opentakeoff-mcp72GreatAuth: no auth required

Verdict: choose Revit for architecture & design work over a hosted endpoint; choose Opentakeoff for architecture & design as a local process. You can also run both side by side in one mcpServers block.

Which MCP server should you choose?

Revit logoChoose Revit when

  • You need dedicated capabilities in the Architecture & Design domain.
  • You prefer a remote HTTP/SSE transport.
  • You have the keys it needs: SCANBIM_USER_KEY.
  • You want tools like revit_upload, revit_get_elements, revit_get_parameters.
Explore Revit

Opentakeoff logoChoose Opentakeoff when

  • You need dedicated capabilities in the Architecture & Design domain.
  • You prefer a local stdio subprocess transport.
  • Its auth model fits you: No auth required.
  • You want tools like load_plan, sheet_info, set_scale.
Explore Opentakeoff

Feature & specification comparison

Rows where the two servers differ are marked.

SpecRevit logoRevitOpentakeoff logoOpentakeoff
CategoryArchitecture & DesignArchitecture & Design
Quality signal51/100 · Good72/100 · Great
Install commandConfirmedConfirmed
TransportRemote HTTP/SSELocal subprocess (stdio)
AuthNot declaredNo auth required
PricingNot declaredFree
Required env varsSCANBIM_USER_KEYNone listed
Verified ownerCommunity listingCommunity listing
GitHub stars0670
Directory activity 1 views 0 installs 0 upvotes 10 views 0 installs 0 upvotes
Tools853

Tools & capabilities

Revit tools 8

  • revit_uploadWhen to use: Ingest a Revit (.rvt / .rfa / .rte / .rft) file into Autodesk Platform Services by downloading it from a publicly reachable URL, uploading it to an OSS bucket, and starting an SVF2 translation so downstream revit_* tools can read elements, parameters, sheets, and views. When NOT to use: Do not call if you already have a translated URN (use the existing model_id instead), if the file is not a Revit source file, or if the URL requires authentication the worker cannot satisfy. APS scopes: data:read data:write data:create bucket:read bucket:create viewables:read (OSS bucket create + object PUT + Model Derivative job). Rate limits: APS default ~50 req/min per app per endpoint; Model Derivative translation jobs ~60 req/min; OSS uploads size-limited per file to 100MB for direct upload, larger via resumable. Errors: 401 APS token expired — refresh credentials and retry. 403 scope insufficient — request data:write + bucket:create. 404 bucket/object not found — confirm bucket was created. 409 bucket exists — safe to ignore, reuse it. 429 rate limited — back off with exponential delay. 5xx APS upstream — retry with jitter up to 3x, then surface. Side effects: Creates a new transient OSS bucket named scanbim-revit-<timestamp>, uploads the object, and starts a Model Derivative translation job. NOT idempotent — each call creates a fresh bucket and new URN.
  • revit_get_elementsWhen to use: After a Revit model has finished translating, fetch the first ~100 elements belonging to a Revit category (e.g. Walls, Doors, Windows, Structural Columns) with their objectid, name, externalId, and property bag. When NOT to use: Do not call before translation completes (manifest.status must be success), and do not use for free-text searches across the whole model — filter by category here or use revit_run_schedule for tabular views. APS scopes: data:read viewables:read (Model Derivative metadata + properties). Rate limits: APS default ~50 req/min per app per endpoint; Model Derivative translation jobs ~60 req/min; OSS uploads size-limited per file to 100MB for direct upload, larger via resumable. Errors: 401 APS token expired — refresh. 403 scope insufficient — add viewables:read. 404 URN not found — confirm model_id and that translation has run. 429 rate limited — back off. 5xx APS upstream — retry with jitter. Side effects: Read-only. Idempotent.
  • revit_get_parametersWhen to use: Enumerate the Revit parameters (type + instance) that appear on elements in a given category, or on one specific element by objectid. Returns unique values per parameter and how many elements carry it — ideal for schema discovery before building a schedule. When NOT to use: Do not use when you just need the elements themselves (use revit_get_elements), or to modify values — this is read-only. APS scopes: data:read viewables:read (Model Derivative metadata + properties). Rate limits: APS default ~50 req/min per app per endpoint; Model Derivative translation jobs ~60 req/min; OSS uploads size-limited per file to 100MB for direct upload, larger via resumable. Errors: 401 APS token expired — refresh. 403 scope insufficient — add viewables:read. 404 URN not found — verify model_id. 429 rate limited — back off. 5xx APS upstream — retry with jitter. Side effects: Read-only. Idempotent.
  • revit_run_scheduleWhen to use: Build a tabular, spreadsheet-style schedule (rows = elements matching a keyword, columns = up to 15 shared parameters) from a translated Revit model — useful for Door Schedules, Wall Schedules, Room Schedules, and QA/QC exports. When NOT to use: Do not use when you only need raw element metadata (use revit_get_elements) or parameter schema (use revit_get_parameters). APS scopes: data:read viewables:read (Model Derivative metadata + properties). Rate limits: APS default ~50 req/min per app per endpoint; Model Derivative translation jobs ~60 req/min; OSS uploads size-limited per file to 100MB for direct upload, larger via resumable. Errors: 401 APS token expired — refresh. 403 scope insufficient — add viewables:read. 404 URN not found — check model_id. 429 rate limited — back off. 5xx APS upstream — retry with jitter. Side effects: Read-only. Idempotent.
  • revit_clash_detectWhen to use: Perform a lightweight clash analysis between two Revit categories (e.g. Mechanical Equipment vs Structural Framing) using bounding-box overlap where available, falling back to shared-Level proximity, then annotate with any matching VDC rules stored in the D1 database. When NOT to use: Do not use as a substitute for Navisworks Manage for contractual clash reports — this is a first-pass coordination sniff test, not a certified Clash Detective run. APS scopes: data:read viewables:read (Model Derivative metadata + properties). Rate limits: APS default ~50 req/min per app per endpoint; Model Derivative translation jobs ~60 req/min; OSS uploads size-limited per file to 100MB for direct upload, larger via resumable. Errors: 401 APS token expired — refresh. 403 scope insufficient — add viewables:read. 404 URN not found — verify model_id. 429 rate limited — back off. 5xx APS upstream — retry with jitter. Side effects: Read-only against APS. May read from D1 vdc_rules table if present. Idempotent.
  • revit_export_ifcWhen to use: Kick off a Model Derivative translation of a previously uploaded Revit URN into IFC (IFC2x3 Coordination View 2.0 by default) so the model can be exchanged with non-Autodesk tools (Solibri, BIMcollab, Tekla, openBIM workflows). When NOT to use: Do not use for SVF/SVF2 web viewing (that happens automatically in revit_upload), and do not call repeatedly while a prior IFC job is still inprogress — poll the manifest instead. APS scopes: data:read data:write viewables:read (Model Derivative job + manifest). Rate limits: APS default ~50 req/min per app per endpoint; Model Derivative translation jobs ~60 req/min; OSS uploads size-limited per file to 100MB for direct upload, larger via resumable. Errors: 401 APS token expired — refresh. 403 scope insufficient — add data:write. 404 URN not found — confirm model_id was translated. 409 not applicable. 429 rate limited — back off. 5xx APS upstream — retry with jitter up to 3x. Side effects: Creates a Model Derivative job and, on completion, a new IFC derivative inside the model's manifest. Safe to re-run (APS deduplicates) but each call with x-ads-force may retranslate.
  • revit_get_sheetsWhen to use: Enumerate the drawing sheets (title blocks with sheet number + sheet name like 'A-101: First Floor Plan') published from a translated Revit model, so an agent can pick which sheet to render, review, or cross-reference. When NOT to use: Do not use to list model views like floor plans or 3D views (use revit_get_views) — this returns only 2D sheet entries. APS scopes: data:read viewables:read (Model Derivative metadata + object tree). Rate limits: APS default ~50 req/min per app per endpoint; Model Derivative translation jobs ~60 req/min; OSS uploads size-limited per file to 100MB for direct upload, larger via resumable. Errors: 401 APS token expired — refresh. 403 scope insufficient — add viewables:read. 404 URN not found — check model_id. 429 rate limited — back off. 5xx APS upstream — retry with jitter. Side effects: Read-only. Idempotent.
  • revit_get_viewsWhen to use: Return every view (both 2D — floor plans, ceiling plans, elevations, sections, sheets — and 3D — default {3D}, perspective views, isometric views) in the translated Revit model, including each view's GUID, name, role, and whether it is the master view. When NOT to use: Do not use when you only want drawing sheets (use revit_get_sheets) or element data inside a view (use revit_get_elements / revit_run_schedule). APS scopes: data:read viewables:read (Model Derivative metadata + object tree). Rate limits: APS default ~50 req/min per app per endpoint; Model Derivative translation jobs ~60 req/min; OSS uploads size-limited per file to 100MB for direct upload, larger via resumable. Errors: 401 APS token expired — refresh. 403 scope insufficient — add viewables:read. 404 URN not found — check model_id. 429 rate limited — back off. 5xx APS upstream — retry with jitter. Side effects: Read-only. Idempotent.

Opentakeoff tools 53

  • load_planOpen a plan PDF from disk. Default: replace the whole session (previous documents, scales, conditions, and shapes are cleared). merge: true ADDS the document to the working set instead (#152) — a bid set is plans + schedule + addenda, not one PDF — keeping every scale, condition, and shape; sheet keys carry file names so documents never collide, the sheet graph spans the whole set (resolve_tag can chain a plan tag on one file to a schedule row in another), and the marked set covers every worked sheet. Re-loading an already-merged file is refused — reload = replace, deliberately. Returns file, files, page_count, and one entry per sheet. The loaded sheets also become browsable resources (takeoff://sheets). Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.
  • sheet_infoSheet detail: dims (px and pt), vector segment count, whether the sheet has vector linework (measure_polygon runs on it; get_sheet_vectors returns the strokes), scale status, the detected scale suggestion, and this sheet's committed shape count. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.
  • set_scaleSet a sheet's scale — exactly ONE of: label (a standard scale, e.g. '1/4" = 1'-0"'), upp (real feet per image px), calibrate (two points along a known dimension plus its real feet), or use_detected (adopt the drawn scale note read off the sheet). The detected scale is never applied automatically — setting it is always this explicit call. Changing an existing scale recomputes measurements and cutout restore quantities from geometry and records one undo_last step. Counts are unchanged. Human-reviewed dimensional work must be recalibrated in the canvas; scales must be finite and positive. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.
  • propose_takeoffOpen a PROPOSAL — a named batch of the shapes you are about to commit, with one identity (#365). Every shape you commit from here on (measure_polygon, measure_line, measure_surface, place_count, the sweeps, the derives, cut_out) attaches to it until you open another proposal or withdraw this one; the estimator then sees ONE Accept pill for the whole batch instead of one per shape — a forty-room pass becomes one decision, not forty. Use it BEFORE the work, the way an estimator titles a takeoff before tracing: "Level 2 rooms per finish schedule A-601", "Base derived from CPT-1 rooms". label is what the estimator reads on the pill; rationale is what decided the batch (the schedule row, the sheet, the rule) — both required, neither is a comment. Nothing here commits geometry or changes a total: an empty proposal is just a heading. The batch is what revise_proposal replaces and withdraw_proposal removes; shapes the estimator has already accepted leave the batch and no agent verb reaches them. takeoff_summary carries the ledger (pending / accepted / withdrawn per batch).
  • measure_polygonMeasure a closed polygon you supply (min 3 vertices, image px): area_sf and perimeter_lf at the sheet's scale. Requires the scale to be set. Pass condition to commit it; role "deduct" subtracts. A room ring belongs on the innermost wall-face strokes from get_sheet_vectors, crossing each door opening on the wall centerline and wrapping columns and stubs; never on a hatch edge, casework or a door leaf. Check it with view_sheet overlay:true on a tight crop and fix it with edit_shape. A CURVED wall is a circle: do not chord it and do not hand-tessellate it — give the bow one point on the wall and list its index in arc_through. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.
  • cut_outCut a REAL hole in a committed floor_area shape (#206) — the way the canvas cuts one (#137): the same lib/cutout.js boolean subtract, so the two surfaces can never disagree about what a hole holds. The parent keeps its outer ring plus the reconciled hole(s) (verts_norm_holes), its computed nets for real — N cuts compose, overlap between cuts never double-deducts (set subtraction), a hole ADDS perimeter — and the deduct commits carrying cuts_shape_id so the report and legend read the reconciled number, never a second arithmetic pass. This is the verb for a column, a floor drain, an island of casework INSIDE a room; an independent measure_polygon role:"deduct" stays the tool for a deduction that isn't a hole in one parent. Refusal over guessing: the ring must sit FULLY inside the parent's outer ring (an edge-crossing cut is a boundary correction — edit_shape the parent instead), and a cut that would erase the parent or split it in two refuses whole (trace the pieces as rooms). One journal entry — undo_last restores parent and hole together; delete_shape on the deduct later reverts the cut too (a multi-cut parent rebuilds from the chain's pristine snapshot minus the survivors). AN OPEN RUN IS CLIPPED, NOT SUBTRACTED: wall tile (surface_area) and base/transitions (linear) are polylines traced in plan, so the ring removes the stretch it covers, the run keeps its id and takes what survives, and a cut through the MIDDLE leaves the far side as its own shape (same condition, same height) — quantities ride the surviving length, which is exact, since wall SF is LF × height and a border's SF is LF × thickness. No deduct is minted for a run: there is no area for one to sit on, and a deduct's SF counts against the FLOOR total a run never fills. A ring that misses the run, one that swallows it whole (delete_shape it), and a curved run (its verts are control points) all refuse. A derived base with numeric openings also refuses: those deductions have no stored location; use measure_line for installed runs so a geometric cut cannot erase the numeric allowance. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.
  • measure_lineMeasure an open polyline (min 2 points, image px): length_lf at the sheet's scale. Requires the scale to be set. Pass condition to commit it as a linear shape (base, transitions, feature strips, conduit and home runs). A curved run (base along a radius wall, a curved feature strip) takes arc_through: one point on the bow, marked. DROP AND RISE (#441): a plan trace is the flat X–Y path; the material also travels VERTICALLY — a home run drops from the ceiling to a panel, rises to a box. length_lf is the TOTAL: plan + rise + drop. The condition's rise_ft / drop_ft (edit_condition) are the defaults for every run under it; pass rise_ft / drop_ft here to give THIS run its own legs (0 included — "no drop on this one" is a statement), and the reply splits plan_lf / vertical_lf beside the total when a leg exists. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.
  • measure_surfaceSurface Area — wall SF (#146): trace an OPEN run along the wall in plan view (min 2 points, image px) and the quantity is traced LF × height. This is how wall tile, wainscot, and wall systems are taken off — the quantity family measure_polygon cannot produce. Height lives on the CONDITION (the canvas's H knob): pass height_ft to set it on this call (journals as its own undo step, like typing H before tracing), or set it once with edit_condition; with neither, this refuses and mints nothing. The shape snapshots the height it was quantified at. Requires the sheet's scale. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.
  • place_countCount markers — EA (#146): one point, one each. Thresholds, stair nosings, floor boxes, entrance mats — the scale-free quantity family. Commits one count shape per point (computed {count: 1}, exactly the canvas's Count tool), NO scale required, and the whole call is ONE undo step. takeoff_summary reports them as ea; the marked set draws each marker. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.
  • symbol_sweepFind EVERY instance of a repeated plan symbol from ONE example — drains, thresholds, fixtures, transition markers: marquee a tight seed_rect around a single instance and the vector linework is searched for every other placement of that same segment cluster. Deterministic geometry, not vision: each placement scores as the length-weighted fraction of the seed's segments reproduced within tolerance_px, under translation plus 0/90/180/270 rotation and mirroring (symbols rotate on plans — both ON by default; turn them off to pin orientation). Score ≥ 0.92 is a match; the 0.75–0.92 band comes back in `withheld` with a reason — a near-match is a question you answer by LOOKING (view_sheet at its `at`), never a silent commit and never a silent drop. RICHER VARIANTS are named, never silent: a placement that reproduces the whole seed but carries >30% extra linework fully inside its footprint (a register against a grille seed — the same outline plus louvers) comes back with its measured `extra` fraction on the row — LOOK at those first, they are the classic mislabel; background lines CROSSING the symbol and coincident duplicate ink never trip this. By default such placements still COUNT, because the contained-seed workflow below depends on supersets matching (seed a bare sub-shape, count the richer symbols that contain it, exclude what you don't mean). Pass `variant_guard: true` when your seed is the WHOLE symbol — grilles, drains, fixtures marqueed complete — and extra-ink placements demote to `withheld` as questions instead of counting; the guard stands down automatically when `exclude` counter-examples are in play, since supplying negatives is manual variant discrimination. The seed's own location is reported in `seed` and never double-committed. Every proposed placement is scored up to a hard work ceiling sized for pathological sheets, and the reply says which it was: complete true means the count is a total; complete false (with candidates.dropped > 0) means the count is a FLOOR — some placements were never scored — so tighten the seed rect around more distinctive geometry rather than trusting it as a total. Marquee discipline: the rect must hug ONE instance — only segments FULLY inside it define the symbol, so a loose rect that swallows wall linework fingerprints the wall, not the symbol. scope "set" sweeps the WHOLE working set, counting on PLAN-role sheets only (the sheet graph decides): a symbol drawn in a detail, legend, or schedule is a reference drawing and never counts itself — which is also how you seed from one: marquee the assembly on the detail sheet and its plan-sheet occurrences are counted while the detail stays excluded (the exclusion disclosed in `skipped`, per-sheet results with per-sheet caps and wall-clock in `sheets`). Scale across sheets: the fingerprint is size-true and is never scale-SEARCHED, so a detail drawn at 1-1/2" = 1'-0" is 12× the size of the same mark on a 1/8" plan — when BOTH sheets have a scale set, the exact ratio is computed from them and the seed is resized before matching (reported per sheet as `scaled`); when a scale is missing, the sweep runs at 1:1 and SAYS so (`scale_assumed`), because an unknown ratio plus a zero count is not evidence of absence. Seeding from a detail/legend/schedule sheet REFUSES outright until both scales are set — that is the case where an unstated ratio silently finds nothing. commit: true (requires condition) commits every match center as an EA count marker through the same path as place_count — the whole sweep (set-wide included) is ONE undo step, each marker carries origin.method "symbol_sweep" with its score, transform, and seed source, and withheld placements are NEVER committed. The SEED instance is not in that count (#296) — in sheet scope it is almost always installed work, so pass commit_seed: true to mint it into the same batch (the reply reminds you whenever a sheet-scope commit leaves it out; ea_total one short of the hand tally is exactly this). The COUNT is scale-free (EA), but matching across sheets of different scales is not — set_scale on the sheets involved is what turns the ratio from an assumption into arithmetic. Counter-examples (#259): drafting reuses one generic shape for different devices — a wall-mounted data outlet drawn as a plain triangle, the flush-floor variant the SAME triangle inside a square, keynote callouts a triangle with a letter in it — so the seed legitimately matches things you do not mean, and seeding more geometry only works where the drawing offers more to capture. `exclude` takes rects around instances you do NOT mean, marqueed exactly like the seed. You never choose a mechanism; the rect's contents decide, because both are the same gesture: a rect holding EXTRA linework beyond the seed rejects placements where that extra linework is present too (the box, the letter), and a rect holding no extra linework of its own is read as the line running THROUGH it — a bare ceiling-grid tile whose grid line a real fixture, drawn over it, would BREAK. That second mechanic is not expressible as a seed: only segments fully INSIDE a rect define a symbol, and background structure is long by nature. Every rejection is disclosed in rejected[] — which negative, what fraction of its evidence was found, and the placement — and NEVER counted in found: an exclusion is a judgement, so look at it and reinstate any you disagree with using place_count at its `at`. A counter-example that holds no instance of the seed, or holds the seed with nothing extra, is REFUSED rather than silently doing nothing. Stroke luminance (#260): a flattened export strips the layer tree and flattens every pen, but the file still STATES stroke color — a black fixture outline over a grey ceiling grid is unambiguous there even when the geometry is identical (two empty 2 ft grid tiles reproduce a 2×4 fixture's outline exactly). luminance_tolerance (0–254) gates on it: a sheet segment only answers for a seed segment when their stroke luminances are within the stated tolerance (Rec. 709, 0 = black, 255 = white; 32–64 separates black from grey without touching anti-aliasing wobble). OPT-IN and disclosed, in the spirit of tolerance_px — omitted, sweeps score exactly as before; stated, the reply's lum_gate says the seed's own luminance band and names every placement the geometry would have committed and the pen did not, so you can LOOK at what a stated gate cost. Prefer geometry (a counter-example, a tighter seed) where the drawing offers it — color is the fallback for exports where nothing else survived. Labels (#308): for a LABELED family — fixtures, tagged equipment, keyed devices — the drawing already names every instance, and the sweep reads those names: a fixture token written beside a placement, or connected to it by a drawn leader line (leader-following arms only on multi-pen sheets, where the annotation pen separates from the work), comes back as `label` + `label_via` on the row, and the seed's own tag rides `seed.label`. Disclosure in both directions, never a recount: a committed match with NO label while the family is labeled was counted on shape alone (measured case: two 0.97 matches that were valve internals, not drains — LOOK at those first), a withheld row carrying the seed's own tag is the drawing vouching for a near-miss (look, then place_count), and a withheld row named a DIFFERENT tag is a sibling fixture answered, not a missed count. After any batch commit, LOOK at what landed — view_sheet {overlay: true} over the swept area — and audit the markers against the drawing before trusting the EA total. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.
  • sweep_schedule_rowTake off a schedule row's mark from the row itself — the estimator's own gesture: a transition type sometimes exists only as a schedule row plus tag markers scattered across the plan sheets, and this tool mints the condition FROM the row and finds every occurrence. Pass the row's key (e.g. 'T1') and the tool (1) reads the row from the set's schedule tables (the sheet_graph/find_schedule machinery — the row is the condition's cited source), (2) anchors a geometric fingerprint on the marker the tag is DRAWN as on a plan sheet (a deterministic pad ladder around the tag text; where the tag occurs more than once the fingerprint must recur at a second occurrence before it is trusted — `anchor.corroborated`), and (3) sweeps every PLAN-role sheet for it. The count is geometry AND text agreeing: drafting reuses one bubble shape across many marks, so a match counts ONLY when the row's own tag sits within the marker footprint (its bbox rides the match as `tag_at` evidence); a match labeled with a SIBLING row's tag is excluded and says whose it is, an unlabeled match is withheld as a question, and a tag drawn with no matching marker is disclosed as text_only. REFUSAL over guessing, with the reason and the fix: no such row; the same key in two tables (ambiguous); a tag drawn on no plan sheet; no repeatable marker linework around the tag — a fingerprint is never guessed from text alone (the fallback is always: marquee one instance with symbol_sweep). commit: true commits the counted matches as EA markers under the row's own key — one undo step for the whole set-wide sweep, every marker carrying origin.assignment {source: "schedule"} plus the anchor and row citation on origin.symbol.seed. The COUNT is scale-free (EA), but matching is not: where the anchor sheet and a target sheet both carry a scale, the marker is resized by their exact ratio before matching (`scaled` per sheet), and where one does not, the sweep runs at 1:1 and discloses it (`scale_assumed`) rather than reporting a confident zero. After committing, LOOK: view_sheet {overlay: true} over each swept sheet. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt. LABEL-FIRST for devices (any trade): when the marker cannot be fingerprinted or does not reach a drawn tag amid linework — a heater bar or fan drawn to its own size, tagged by a leader — every such tag on a plan sheet counts as ONE instance BY LABEL, disclosed in found_by_label / label_only / counted_by; a bare mention in a note (no linework near it) is text_only, never a count. Rows come from every schedule family the sheet graph reads: room-finish, finish/material, and equipment (mechanical, electrical, plumbing, fire).
  • count_marksThe COUNT TAKEOFF in one deterministic call — no seeds, no model, seconds: census every VALUE-ANNOTATED mark tag on the plan-role sheets, counted per schedule mark, committed as EA markers when asked. The identity rule is the annotated-device drafting pattern: a device is drawn as its mark tag with a value under it ("S1" over "200" — CFM on air devices, GPM on fixtures, a rating on equipment), so a tag WITH a paired value counts, a tag inside a schedule table's own region is a row label (excluded, tallied), and every other occurrence is WITHHELD with a reason and coordinates — a tag amid linework but unvalued may be a real device (view_sheet it), a bare tag is probably a note mention. Marks default to the set's schedule row keys (a compound row "R1 / E1" answers for R1 AND E1; each mark cites its row), or state them: {marks: ["S1","R1"]}. The complement to sweep_schedule_row: THAT tool is for marks drawn ON their marker with no value (finish tags in bubbles) and matches geometry; this one is for annotated devices and needs no fingerprint at all. Refusal-honest: scans refuse (no text layer), a set with no mark-shaped rows refuses unless marks are stated, non-plan sheets are skipped with the role that excused them. commit: true commits every counted occurrence under its mark's own tag — ONE undo step for the whole census, schedule citation on origin. Counts are scale-free (EA) — no set_scale needed. Then AUDIT: view_sheet {overlay: true} where the markers landed, and read every withheld entry — a withheld item you ignore is a hole in the bid. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt. EQUIPMENT marks (a row in an equipment/device schedule — fans, pumps, heaters, fixtures, panels) follow the leader-tag convention instead: a scheduled mark drawn amid linework with no value under it is counted BY LABEL (occurrence by: "label", counted_by_label on the mark); a bare mention in a note still withholds.
  • +41 more on the listing page

Ready-to-paste client configs

Paste either block (or both) into your client config, e.g. claude_desktop_config.json or ~/.cursor/mcp.json. We only show a config when the install command is confirmed.

Revit

mcpServers (Claude Desktop / Cursor)
{
  "mcpServers": {
    "revit": {
      "url": "https://revit-mcp.itmartin24.workers.dev/mcp"
    }
  }
}

Opentakeoff

mcpServers (Claude Desktop / Cursor)
{
  "mcpServers": {
    "kentucky-ai-opentakeoff": {
      "command": "npx",
      "args": [
        "-y",
        "opentakeoff-mcp"
      ]
    }
  }
}

Frequently asked questions

Revit is categorized under Architecture & Design and uses a remote HTTP/SSE transport. Opentakeoff belongs to Architecture & Design and uses a local stdio subprocess. Pick Revit for architecture & design work and Opentakeoff for architecture & design.

Copy the mcpServers block from the config section on this page into your client's config (for example claude_desktop_config.json or ~/.cursor/mcp.json), then fully restart the client.

Revit has no declared pricing — check the project's own site. Opentakeoff is listed as free. Auth: no auth required. If a server needs environment variables such as API keys, define them under the env key in your client's MCP config.

Yes. MCP clients support multiple servers: add revit and kentucky-ai-opentakeoff as separate keys in the same mcpServers object, and your assistant will call tools from either one.

On AllMCPs, Revit has a quality signal of 51/100 (Good) with 1 views, 0 installs and 0 GitHub stars. Opentakeoff has 72/100 (Great) with 10 views, 0 installs and 670 GitHub stars.