HZPlan
Turn a photo, measurements, or a description into an editable 2D floor plan — name rooms, place objects, and view it in 3D.
Что умеет
- Create Plan: Use this when the user wants to start a brand-new house plan from scratch. Creates an empty private plan owned by the current user and returns its plan_id; follow up with create_storey, t
- List Plans: Use this when you need to find a plan_id or show the user what plans they have. Lists the current user's private plans with their ids and titles. Read-only.
- Request Image Upload: Get a one-time upload link for a floor-plan image when you can't attach it directly or the file is too large to pass inline. Works for ANY client (Claude, ChatGPT, etc.). Quota-f
Какие данные видит
Нужен ли аккаунт
Не нужен: сервер работает без входа
Turn a photo, measurements, or a description into an editable 2D floor plan — name rooms, place objects, and view it in 3D. Long description: HZplan is a house-layout assistant. Describe a home or upload a floor-plan photo and HZplan builds a structured, editable plan: storeys, walls, openings, rooms, and objects with real metric dimensions. Ask it to add a wall, place a desk, name a room, or publish a shareable view. Plans render to SVG, an interactive web view, and a 3D walkthrough.
Как подключить
Как подключить HZPlan к Claude
Адрес сервера, который нужно вставить в настройках Claude:
https://hzplan--goncharov-artemv.run.tools
- Откройте Claude (сайт claude.ai или приложение для компьютера).
- Перейдите в Настройки → Коннекторы (в английском интерфейсе: Settings → Connectors).
- Нажмите Добавить пользовательский коннектор (Add custom connector).
- Вставьте адрес сервера, скопированный ниже, в поле URL удалённого MCP-сервера и нажмите Добавить.
- В чате нажмите + → Коннекторы и включите новый коннектор.
Пользовательские коннекторы доступны на тарифах Free, Pro, Max, Team и Enterprise. На Free можно добавить только один. В Team и Enterprise коннектор сначала добавляет владелец организации в Настройки организации → Коннекторы.
Авторизация
После нажатия Добавить откроется окно входа в сервис. Войдите под своим аккаунтом и подтвердите доступ. Пароль Claude не видит.
Как подключить HZPlan к ChatGPT
Адрес сервера, который нужно вставить в настройках ChatGPT:
https://hzplan--goncharov-artemv.run.tools
- Откройте ChatGPT в браузере (chatgpt.com). Нужен тариф Plus, Pro, Business, Enterprise или Edu.
- Один раз включите режим разработчика: Настройки → Приложения → Дополнительные настройки → Режим разработчика (Settings → Apps → Advanced settings → Developer mode).
- Откройте Настройки → Коннекторы и нажмите Создать (Create).
- Заполните форму: Название (любое), Описание (одна фраза, что делает сервис), URL MCP-сервера (скопируйте ниже).
- В поле Аутентификация выберите OAuth, если сервис требует вход, иначе Нет. Нажмите Создать.
- В новом чате откройте + → Приложения/Коннекторы и включите коннектор.
Название раздела у OpenAI менялось (Connectors → Apps/Plugins). Если пункт называется иначе, ищите «режим разработчика» в настройках. В рабочих пространствах Business/Enterprise администратор должен разрешить пользовательские коннекторы.
Авторизация
При первом использовании ChatGPT откроет окно входа в сервис. Войдите и подтвердите доступ.
Как подключить HZPlan к Cursor
Адрес сервера, который нужно вставить в настройках Cursor:
https://hzplan--goncharov-artemv.run.tools
Быстрый способ: нажмите кнопку Открыть в Cursor ниже, Cursor сам предложит добавить сервер.
Вручную:
- В Cursor откройте Settings → Cursor Settings → MCP и нажмите Add new global MCP server.
- Вставьте JSON, скопированный ниже, в открывшийся файл
~/.cursor/mcp.json(для одного проекта:.cursor/mcp.jsonв корне проекта). - Сохраните файл. Сервер появится в списке MCP; при необходимости пройдите авторизацию по кнопке рядом с ним.
Авторизация
Если сервис требует вход, рядом с сервером в списке MCP появится кнопка авторизации.
Список инструментов сервера (31)
Технические названия из tools/list. Нужны только разработчикам.
| create_plan | Use this when the user wants to start a brand-new house plan from scratch. Creates an empty private plan owned by the current user and returns its plan_id; follow up with create_storey, then add walls/openings/objects via upsert_entity. To build a plan from a floor-plan image instead, use split_floor_plan + convert_floor_to_plan. |
| list_plans | Use this when you need to find a plan_id or show the user what plans they have. Lists the current user's private plans with their ids and titles. Read-only. |
| request_image_upload | Get a one-time upload link for a floor-plan image when you can't attach it directly or the file is too large to pass inline. Works for ANY client (Claude, ChatGPT, etc.). Quota-free. FLOW 1. Call this (no args) → { upload_id, upload_url }. 2. Show the user the upload_url and ask them to open it and drop their floor-plan image (one tap). 3. Then call split_floor_plan with image_ref: { upload_id }. If they haven't finished you'll get code 'upload_pending' — wait a moment and retry. 4. Reuse the SAME upload_id for convert_floor_to_plan — one upload covers split + convert. ChatGPT: prefer attaching the file (pass `image`). But if that keeps failing or the file is too large, FALL BACK to this upload link — the user just opens it and drops the image, and you only ever pass back image_ref:{ upload_id } (no base64). LAST-RESORT FALLBACK: if the user can't or won't use the link, tell them they can create the plan directly in the web app: https://hzplan.uk/plans/new/from-image — never leave them with no way forward. |
| send_feedback | Send the user's feedback to the HZplan developers. Use when a tool failed, behaved unexpectedly, was confusing, or the user has a complaint, suggestion, or praise. Quota-free. CONSENT — REQUIRED: first draft the message, show the user the EXACT text you will send, and get their explicit agreement. Never send feedback the user hasn't seen and approved, and never include anything they didn't agree to share. |
| split_floor_plan | Step 1 of 2 in the image → hzplan pipeline. Detects how many distinct floor plans are present on a single uploaded image (residential plans frequently show ground + first + sometimes basement on one page) and returns one bounding box + preview crop per detected floor. Quota-free. WHEN TO CALL Call this ONCE per image the user wants to convert into an hzplan. If it returns ok:true, USE the floors[] result — show previews to the user, ask which to import, then call convert_floor_to_plan for each chosen floor. DO NOT retry a successful call. DO NOT issue duplicate calls back-to-back. WHAT TO PASS image: the user's uploaded image (ChatGPT fills download_url + file_id automatically; the model never has to base64-encode anything). Other clients can use `raw_url` (data: URI or http URL) or `raw_base64` instead — non-ChatGPT only. FALLBACK if the image is hard to pass (ChatGPT base64 failing, file too large): call request_image_upload, have the user drop the image on the link, then pass image_ref:{ upload_id }. As a last resort, point the user to the web app: https://hzplan.uk/plans/new/from-image WHAT YOU GET BACK ON SUCCESS { ok: true, floors: [ { floor_id, suggested_name, bbox_px, source } ] } PLUS one preview image per floor attached as image content (in the same order as floors[]). - floor_id: opaque string. PASS THIS to convert_floor_to_plan to convert that specific floor. - suggested_name: 'ground' / 'first' / 'second' / 'third' / 'fourth' or 'floor N' for >5 floors. Use as the default storey_name unless the user overrides. - bbox_px: [x1, y1, x2, y2] location of the floor in the source image's pixel space. - the attached preview images let the user confirm which floor is which; floors[][i] corresponds to attached image i. NEXT STEPS AFTER A SUCCESSFUL CALL 1. Show the user the attached floor preview images (one per entry in floors[], same order). 2. Ask which floors to import and confirm storey names. 3. For each chosen floor, call conv |
| convert_floor_to_plan | Step 2 of 2 in the image → hzplan pipeline. Runs full inference + conversion on ONE floor (picked by floor_id from a prior split_floor_plan response) and either mints a new hzplan (plan_id omitted) or appends the floor as a new storey to an existing plan (plan_id given). Burns 1 unit of `plan_from_image_monthly_*` quota per call. WHEN TO CALL After split_floor_plan returned successfully, the user has seen the previews, and they've told you which floor(s) to import. Call this ONCE per chosen floor. For a 3-floor plan the user wants fully imported, that's 1 split_floor_plan call + 3 convert_floor_to_plan calls. DO NOT retry a successful call. DO NOT call this before split_floor_plan. WHAT TO PASS image: the SAME image used in split_floor_plan (ChatGPT auto-fills again from the user's upload). floor_id: the floor_id from the split_floor_plan response. storey_name: human label for this storey, e.g. 'ground', 'first', 'kitchen floor'. Default to the suggested_name from split_floor_plan if the user doesn't say otherwise. plan_id: OMIT on the first conversion (mints a brand-new plan). PASS the previous call's plan_id on all subsequent conversions so all floors land on ONE plan as separate storeys. plan_title: only relevant when plan_id is omitted — title for the new plan. WHAT YOU GET BACK ON SUCCESS { ok: true, plan_id, storey_id, quota, summary } - plan_id: pass this to the NEXT convert_floor_to_plan call if there are more floors to import. - storey_id: the storey id assigned inside the plan ('s1', 's2', ...). - quota: current usage info. - summary: diagnostic counts (walls, openings, mm_per_px) — usually fine to ignore. NEXT STEPS - After all chosen floors are converted, tell the user the plan is ready. Render it with render_storey_widget(plan_id, storey_id) to show the result. - The user can then refine the plan with upsert_entity / delete_entity / set_object_feature_side etc. — those operate on walls / openings / objects inside the stor |
| rescale_storey | Multiplies every length / size / offset on `storey_id` by `factor`. Wall thicknesses, image bboxes, angles, and other absolute / unitless values are NOT touched. Runs the geometry engine after the rescale and refuses to commit if the storey becomes infeasible. WHEN TO CALL After convert_floor_to_plan if the AI inspects the image and decides the plan's scale is wrong (door-width heuristic is typically ±20% off). Also usable on hand-authored plans where the user wants to scale a storey up/down. Call ONCE per scale change — if the call succeeds, the plan is updated; if it fails (solver_infeasible / solver_ambiguous), the plan is unchanged. WHAT TO PASS plan_id: the plan owning the storey. storey_id: the storey to rescale (e.g. 's1', 's2'). factor: number in [0.1, 10]. Compute as `target_real_world_length / current_plan_length`. Example: AI sees a wall on the image and thinks it's 4000 mm in real life; the plan currently reports that wall as 3500 mm; factor = 4000 / 3500 ≈ 1.143. WHAT YOU GET BACK ON SUCCESS { ok: true, plan_id, storey_id, content_version, scaled_count: { length_mm, size_mm, offset }, storey_size_mm: [w, h] } - scaled_count reports how many of each predicate were touched (sanity check the factor was applied to walls + openings). - storey_size_mm is the new outer dimensions in mm. ERROR CODES plan_not_found / storey_not_found: bad ids. invalid_factor: factor out of [0.1, 10]. solver_infeasible: rescaled storey is overconstrained (e.g. a fixed-size object no longer fits). Plan was NOT modified. Try a different factor or fix the object first. solver_ambiguous: rescaled storey is underconstrained. Add missing offsets and retry, or revert. solver_error: the geometry engine itself failed; surface to user. Do NOT retry on success. Compose multiple rescales by calling once per storey. |
| list_illustrations | List accessible system illustrations and the current user's private illustrations. Use this tool for illustration discovery; do not use MCP resources for library data. |
| get_illustration | Get one illustration, including its stored SVG markup. Use this tool for illustration data; MCP resources are widget HTML only. |
| set_object_illustration | Attach, replace, or clear an illustration on an existing object while preserving the object's other assertions automatically. Prefer this over upsert_entity when you only want to change illustration_id. |
| set_object_feature_side | Update only the directional `feature_side` on an existing object (top/bottom/left/right, or null to clear) while preserving every other assertion — kind, size_mm, offsets, illustration_id, model_3d, etc. ALWAYS prefer this over upsert_entity when changing only the facing direction; upsert_entity replaces the whole assertion list and will silently drop attachments like model_3d. |
| set_object_height | Update only the `height_mm` on an existing object (positive integer of millimetres, or null to clear and fall back to the kind default) while preserving every other assertion — kind, size_mm, offsets, illustration_id, model_3d, feature_side, etc. ALWAYS prefer this over upsert_entity when changing only the rendered height; upsert_entity replaces the whole assertion list and will silently drop attachments like model_3d. Use this when a generated 3D model looks too tall or too short in the 3D walk — the 3D generation parameter only changes the GLB's baked proportions; the renderer scales to whatever this assertion (or the kind default) says. |
| upsert_illustration | Create or update a private SVG illustration in the current user's library. Omit illustration_id to create a new illustration (name and svg_markup are then required); pass illustration_id to update an existing one (only the fields you include change, the rest are preserved). render_svg_preview only previews — this tool persists. |
| delete_illustration | Archive one private illustration from the current user's library. |
| get_plan | Get the stored top-level summary for one plan. This returns summary data, not the full plan document. |
| create_storey | Create a new empty storey in an existing plan and return its current storey state. After create_storey, immediately call upsert_entity with type=storey and size_mm(width_mm, depth_mm) so the geometry engine has a fixed storey rectangle. Then place outer walls relative to storey anchors like s1.left, s1.right, s1.top, and s1.bottom. |
| get_storey_plan | Get bounded structured detail for one storey. Use storey_id from get_plan when possible. |
| name_space_at_point | Create or update a semantic space name by dropping an interior pin on a solved storey. Use this for named floor areas such as bathroom, bedroom, hall, kitchen, or garage after walls and openings already define the enclosure. This does not author new geometry. Instead it stores a semantic pin and binds the name onto the derived free-space region that contains that point. The point must fall inside exactly one derived free-space region. Provide space_id to rename or move an existing named space; omit it to create a new one. |
| get_plan_topology | Get the plan's spatial room topology. Returns one node per derived free-space region (the rooms shown in the UI) with its storey, an interior `center` point, area, and bound `name`/`kind` (null until named); and edges for the openings/doorways connecting rooms. To name a room, pass that node's `center` as the (x_mm, y_mm) to name_space_at_point. |
| list_room_objects | List current objects in one room. |
| get_object | Get one object with its current constraints and solved placement metadata. Use set_object_illustration for illustration-only changes; use this before upsert_entity when you need to preserve existing assertions during broader object edits. |
| render_svg_preview | Render one illustration SVG preview in the ChatGPT widget. Preview only; this does not save anything. Use upsert_illustration to persist SVG markup. |
| render_storey_widget | Render one storey in the ChatGPT widget. |
| render_named_room_widget | Render one named room from a storey in the ChatGPT widget. |
| render_plan_topology_widget | Render the whole-plan topology graph in the ChatGPT widget. |
| get_authoring_guide | Call this first when authoring or editing plans if you need the supported predicates, recommended sequencing, or the MCP usage rule that resources are widget shells only while tools handle all reads and writes. |
| upsert_entity | Create or update one authored entity in the current plan head. Supported phase-1 entity types are storey, wall, opening, and object. Assertions are entity-local: do not repeat entity_id in args. Use tools for all data reads and writes; MCP resources are widget HTML only and are never required for CRUD. When changing only an existing object's illustration link, prefer set_object_illustration instead of rebuilding the full assertion list manually. Storey workflow: after create_storey, upsert the storey with size_mm(width_mm, depth_mm) before adding structural entities. Only walls and openings create or divide rooms. Use name_space_at_point(...) to name enclosed floor areas such as bathroom, bedroom, hall, kitchen, or garage after walls and openings define them. Do not create those spaces as objects. Objects are furniture, fixtures, and equipment such as cupboards, sofas, beds, tables, toilets, sinks, and tubs; they may align to walls or surrounding spaces but they do not replace partition walls. Some furniture is directional: use feature_side(top|bottom|left|right) as the canonical rendered-facing predicate when orientation matters. When updating an existing entity, fetch it first and resend every assertion you want to keep. Storey predicates: ceiling_height_mm(number_mm), eye_height_mm(number_mm), size_mm(width_mm, depth_mm). Wall predicates: on_storey(storey_id), kind(value), axis(horizontal|vertical), angle_deg(number), thickness_mm(number_mm), length_mm(number_mm), height_mm(number_mm), offset(anchor, reference_anchor, delta_mm). Offsets constrain solved rectangle edges, not wall centerlines, so think in left, right, top, and bottom edges. When you want a gap between two entities, offset the closest pair of edges that represents that gap. Outer walls should usually be positioned relative to storey anchors, for example offset(left, s1.left, 0) or offset(bottom, s1.bottom, 0). For interior wall corners, place the first wall from storey anchors, then place the closin |
| upsert_entities | Apply many entity creations/updates to one plan in a single call. Intended for blast-then-repair authoring when importing from a floor plan image: emit the whole plan (storey size + all walls + openings + objects) as one batch, read the per-item and global solver report, then resubmit a follow-up batch that only contains the corrections. Items are applied in order against the same in-memory document; if any item fails to apply OR the solver reports infeasible/ambiguous/overlapping storeys, nothing is persisted and the full per-item + per-storey report is returned so you can fix and retry. Prefer create_storey first (so s1 exists) and pass entity_id on items you want to update instead of create. |
| delete_entity | Delete one wall, opening, or object from the current plan head by its entity id — this is the single tool for removing in-storey entities. To remove an entire floor and everything on it, use delete_storey instead. Deletion fails if another entity still depends on this one (for example an opening still hosting on a wall, or an offset(...) anchored to it); the response then lists the blocking entity ids so you can re-host or re-anchor them, then retry. |
| delete_storey | Use this when the user wants to remove an entire floor. Deletes one storey and everything on it — its rooms, spaces, objects, walls, and openings — from the current plan head. This is destructive and is not reversible through the API. It fails only if an entity on another storey still references something on this one; the response then lists the blocking entity ids so they can be re-anchored or deleted first, then retry. |
| delete_plan | Use this when the user wants to permanently delete an entire plan. Removes the plan and all of its storeys, along with any uploaded floor-plan images and public snapshots tied to it. This is destructive and cannot be undone. Prefer this over deleting storeys one by one when the whole plan should go; confirm with the user first because there is no recovery. |