roomvana-mcp 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .pytest_cache/
6
+ dist/
7
+ build/
8
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Roomvana
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.5
2
+ Name: roomvana-mcp
3
+ Version: 0.1.2
4
+ Summary: MCP server for Roomvana — browse room types & design styles and get a direct link to the Roomvana studio. Redesign your house, yard or any room from a photo at https://roomvana.ai
5
+ Project-URL: Homepage, https://roomvana.ai
6
+ Project-URL: Source, https://github.com/TJLDC/roomvana-mcp
7
+ Project-URL: Issues, https://github.com/TJLDC/roomvana-mcp/issues
8
+ Author: Roomvana
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai,home-design,interior-design,mcp,modelcontextprotocol,roomvana
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Artistic Software
24
+ Classifier: Topic :: Internet
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: httpx>=0.27
27
+ Requires-Dist: mcp<3,>=2.3.0
28
+ Description-Content-Type: text/markdown
29
+
30
+ # Roomvana MCP server (Python)
31
+
32
+ An [MCP](https://modelcontextprotocol.io) server for **[Roomvana](https://roomvana.ai)** — AI home design for houses, yards and rooms. It lets an AI assistant browse the room types and design styles Roomvana supports and hand the user a studio link with their choices pre-selected. Upload a photo of your house, yard or any room and see it redesigned at **[roomvana.ai](https://roomvana.ai)**.
33
+
34
+ Read-only and no sign-in required: this server never uploads photos or spends credits. The actual redesign happens on the website.
35
+
36
+ ## Tools
37
+
38
+ | Tool | Parameters | What it does |
39
+ | --- | --- | --- |
40
+ | `list_design_styles` | none | Lists the design styles Roomvana can render (modern, japandi, industrial, coastal, …), each with a short description. |
41
+ | `list_room_types` | none | Lists the rooms and outdoor spaces Roomvana can redesign — interior rooms, house exterior, and garden/patio/backyard. |
42
+ | `design_a_room` | `room` (string, required), `style` (string, optional), `notes` (string, optional) | Returns a [roomvana.ai](https://roomvana.ai) studio link with the chosen room and style pre-selected; validates both against Roomvana's live catalog. `notes` is free text that is echoed back to the user, not encoded in the link. |
43
+
44
+ ## Use the hosted endpoint
45
+
46
+ Roomvana also runs the same tools over Streamable HTTP at `https://api.roomvana.co/mcp` — no install, no API key.
47
+
48
+ Claude Code:
49
+
50
+ ```bash
51
+ claude mcp add --transport http roomvana https://api.roomvana.co/mcp
52
+ ```
53
+
54
+ Or point any MCP client at it with a JSON config:
55
+
56
+ ```json
57
+ {
58
+ "mcpServers": {
59
+ "roomvana": {
60
+ "type": "http",
61
+ "url": "https://api.roomvana.co/mcp"
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ ## Run locally
68
+
69
+ Requires Python 3.10+ and [uv](https://docs.astral.sh/uv/).
70
+
71
+ ```json
72
+ {
73
+ "mcpServers": {
74
+ "roomvana": {
75
+ "command": "uvx",
76
+ "args": ["roomvana-mcp"]
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+ Add that to your MCP client config (Claude Desktop: `claude_desktop_config.json`; other clients: their MCP config file), then restart the client.
83
+
84
+ ### From source
85
+
86
+ ```bash
87
+ uv sync
88
+ uv run roomvana-mcp
89
+ ```
90
+
91
+ ## Example
92
+
93
+ > "I want to redo my bedroom in a japandi style."
94
+
95
+ The assistant calls `design_a_room` with `room=bedroom`, `style=japandi` and returns:
96
+
97
+ ```
98
+ https://roomvana.ai/design?room=bedroom&style=japandi
99
+ ```
100
+
101
+ Open it, upload a photo of the room, and generate a redesign. Your walls, windows and layout stay put — only the look changes.
102
+
103
+ ## How it works
104
+
105
+ The list of supported rooms and styles is fetched live from Roomvana's public catalog endpoint (`https://api.roomvana.co/options`), so the server never offers something Roomvana can't render. If that endpoint is unreachable, it falls back to a bundled snapshot.
106
+
107
+ ## Links
108
+
109
+ - Website: https://roomvana.ai
110
+ - Design studio: https://roomvana.ai/design
111
+
112
+ ## License
113
+
114
+ MIT
@@ -0,0 +1,85 @@
1
+ # Roomvana MCP server (Python)
2
+
3
+ An [MCP](https://modelcontextprotocol.io) server for **[Roomvana](https://roomvana.ai)** — AI home design for houses, yards and rooms. It lets an AI assistant browse the room types and design styles Roomvana supports and hand the user a studio link with their choices pre-selected. Upload a photo of your house, yard or any room and see it redesigned at **[roomvana.ai](https://roomvana.ai)**.
4
+
5
+ Read-only and no sign-in required: this server never uploads photos or spends credits. The actual redesign happens on the website.
6
+
7
+ ## Tools
8
+
9
+ | Tool | Parameters | What it does |
10
+ | --- | --- | --- |
11
+ | `list_design_styles` | none | Lists the design styles Roomvana can render (modern, japandi, industrial, coastal, …), each with a short description. |
12
+ | `list_room_types` | none | Lists the rooms and outdoor spaces Roomvana can redesign — interior rooms, house exterior, and garden/patio/backyard. |
13
+ | `design_a_room` | `room` (string, required), `style` (string, optional), `notes` (string, optional) | Returns a [roomvana.ai](https://roomvana.ai) studio link with the chosen room and style pre-selected; validates both against Roomvana's live catalog. `notes` is free text that is echoed back to the user, not encoded in the link. |
14
+
15
+ ## Use the hosted endpoint
16
+
17
+ Roomvana also runs the same tools over Streamable HTTP at `https://api.roomvana.co/mcp` — no install, no API key.
18
+
19
+ Claude Code:
20
+
21
+ ```bash
22
+ claude mcp add --transport http roomvana https://api.roomvana.co/mcp
23
+ ```
24
+
25
+ Or point any MCP client at it with a JSON config:
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "roomvana": {
31
+ "type": "http",
32
+ "url": "https://api.roomvana.co/mcp"
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ ## Run locally
39
+
40
+ Requires Python 3.10+ and [uv](https://docs.astral.sh/uv/).
41
+
42
+ ```json
43
+ {
44
+ "mcpServers": {
45
+ "roomvana": {
46
+ "command": "uvx",
47
+ "args": ["roomvana-mcp"]
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ Add that to your MCP client config (Claude Desktop: `claude_desktop_config.json`; other clients: their MCP config file), then restart the client.
54
+
55
+ ### From source
56
+
57
+ ```bash
58
+ uv sync
59
+ uv run roomvana-mcp
60
+ ```
61
+
62
+ ## Example
63
+
64
+ > "I want to redo my bedroom in a japandi style."
65
+
66
+ The assistant calls `design_a_room` with `room=bedroom`, `style=japandi` and returns:
67
+
68
+ ```
69
+ https://roomvana.ai/design?room=bedroom&style=japandi
70
+ ```
71
+
72
+ Open it, upload a photo of the room, and generate a redesign. Your walls, windows and layout stay put — only the look changes.
73
+
74
+ ## How it works
75
+
76
+ The list of supported rooms and styles is fetched live from Roomvana's public catalog endpoint (`https://api.roomvana.co/options`), so the server never offers something Roomvana can't render. If that endpoint is unreachable, it falls back to a bundled snapshot.
77
+
78
+ ## Links
79
+
80
+ - Website: https://roomvana.ai
81
+ - Design studio: https://roomvana.ai/design
82
+
83
+ ## License
84
+
85
+ MIT
@@ -0,0 +1,58 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "roomvana-mcp"
7
+ version = "0.1.2"
8
+ description = "MCP server for Roomvana — browse room types & design styles and get a direct link to the Roomvana studio. Redesign your house, yard or any room from a photo at https://roomvana.ai"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Roomvana" }]
14
+ keywords = [
15
+ "mcp",
16
+ "modelcontextprotocol",
17
+ "interior-design",
18
+ "ai",
19
+ "home-design",
20
+ "roomvana",
21
+ ]
22
+ classifiers = [
23
+ "Development Status :: 5 - Production/Stable",
24
+ "Environment :: Console",
25
+ "Intended Audience :: End Users/Desktop",
26
+ "License :: OSI Approved :: MIT License",
27
+ "Operating System :: OS Independent",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3 :: Only",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Programming Language :: Python :: 3.13",
34
+ "Topic :: Artistic Software",
35
+ "Topic :: Internet",
36
+ ]
37
+ dependencies = [
38
+ "httpx>=0.27",
39
+ "mcp>=2.3.0,<3",
40
+ ]
41
+
42
+ [project.scripts]
43
+ roomvana-mcp = "roomvana_mcp:main"
44
+
45
+ [project.urls]
46
+ Homepage = "https://roomvana.ai"
47
+ Source = "https://github.com/TJLDC/roomvana-mcp"
48
+ Issues = "https://github.com/TJLDC/roomvana-mcp/issues"
49
+
50
+ [dependency-groups]
51
+ dev = ["pytest>=8"]
52
+
53
+ [tool.hatch.build.targets.wheel]
54
+ packages = ["src/roomvana_mcp"]
55
+
56
+ [tool.pytest.ini_options]
57
+ testpaths = ["tests"]
58
+ pythonpath = ["src"]
@@ -0,0 +1,19 @@
1
+ """Roomvana MCP server (Python port of the ``roomvana-mcp`` npm package)."""
2
+
3
+ from .server import (
4
+ SERVER_NAME,
5
+ SERVER_VERSION,
6
+ TOOL_NAMES,
7
+ TOOLS,
8
+ create_server,
9
+ main,
10
+ )
11
+
12
+ __all__ = [
13
+ "main",
14
+ "create_server",
15
+ "TOOL_NAMES",
16
+ "TOOLS",
17
+ "SERVER_NAME",
18
+ "SERVER_VERSION",
19
+ ]
@@ -0,0 +1,4 @@
1
+ from . import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,198 @@
1
+ """Roomvana's room/style taxonomy.
2
+
3
+ The AUTHORITATIVE list of what exists is the live, public, no-auth endpoint
4
+ ``GET https://api.roomvana.co/options`` (the API domain stays on .co forever --
5
+ the shipped iOS build has it compiled in). We fetch it so this server never
6
+ offers a room or style the product can't actually render. The maps below are a
7
+ hand-maintained MIRROR used only to attach a human-readable label to each id
8
+ (the /options payload carries ids only); an id present live but missing here
9
+ still gets listed, just with a title-cased fallback label. Refresh these when
10
+ app/options.py changes -- they are enrichment, not the source of truth.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import re
16
+ import time
17
+ from typing import Any
18
+ from urllib.parse import urlencode
19
+
20
+ import httpx
21
+
22
+ API_BASE = "https://api.roomvana.co"
23
+ SITE_BASE = "https://roomvana.ai"
24
+
25
+ # style id -> short description (mirror of app/options.py STYLES)
26
+ STYLE_LABELS: dict[str, str] = {
27
+ "modern": "sleek modern — clean lines, uncluttered surfaces, neutral palette with bold accents",
28
+ "minimalist": "minimalist — essential furniture only, calm neutral tones, hidden storage",
29
+ "scandinavian": "Scandinavian — light woods, white walls, cozy textiles, functional simplicity",
30
+ "japandi": "Japandi — Japanese-Scandinavian fusion, warm woods, low-profile furniture, serene neutrals",
31
+ "mid-century-modern": "mid-century modern — walnut and teak furniture, organic curves, retro accents",
32
+ "industrial": "industrial loft — exposed brick and metal, dark tones, raw finishes",
33
+ "bohemian": "bohemian — layered patterns and textures, plants, warm eclectic decor",
34
+ "modern-farmhouse": "modern farmhouse — shiplap accents, warm woods, black fixtures, cozy rustic-meets-clean",
35
+ "coastal": "coastal — airy whites and blues, natural fibers, beach-house freshness",
36
+ "traditional": "traditional — classic furniture silhouettes, rich woods, elegant symmetry",
37
+ "contemporary": "contemporary — current-day polish, mixed materials, comfortable sophistication",
38
+ "rustic": "rustic — reclaimed wood, stone textures, warm cabin comfort",
39
+ "french-country": "French country — soft pastels, carved wood, linen fabrics, Provencal charm",
40
+ "mediterranean": "Mediterranean — terracotta, wrought iron, warm plaster walls, arched details",
41
+ "art-deco": "Art Deco — geometric patterns, brass and velvet, glamorous jewel tones",
42
+ "modern-luxury": "modern luxury — marble, brass details, statement lighting, hotel-suite polish",
43
+ "vintage": "vintage retro — curated second-hand furniture, nostalgic colors, timeless character",
44
+ "dark-academia": "dark academia — moody deep tones, wood paneling, library-inspired warmth",
45
+ "tropical": "tropical — lush green plants, rattan and bamboo, resort-like brightness",
46
+ "cottagecore": "cottagecore — floral patterns, soft vintage furniture, whimsical countryside coziness",
47
+ "transitional": "transitional — traditional silhouettes with contemporary lines, soft neutral palette",
48
+ "spanish-colonial-revival": "Spanish Colonial Revival — white stucco, dark carved wood beams, terracotta tile, arched openings",
49
+ "2026-trends": "2026 interior trends — organic curved furniture, earthy color-drenched walls, sculptural lighting",
50
+ }
51
+
52
+ # room/space id -> human-readable name (mirror of app/options.py)
53
+ ROOM_LABELS: dict[str, str] = {
54
+ # interior
55
+ "kitchen": "kitchen",
56
+ "living-room": "living room",
57
+ "bedroom": "bedroom",
58
+ "bathroom": "bathroom",
59
+ "dining-room": "dining room",
60
+ "home-office": "home office",
61
+ "kids-room": "kids room",
62
+ "nursery": "nursery",
63
+ "basement": "basement",
64
+ "attic": "attic bedroom",
65
+ "entryway": "entryway",
66
+ "laundry-room": "laundry room",
67
+ "closet": "walk-in closet",
68
+ "home-theater": "home theater room",
69
+ "home-gym": "home gym",
70
+ "wine-cellar": "wine cellar",
71
+ "basement-bar": "basement bar",
72
+ "staircase": "staircase and stairwell",
73
+ # exterior
74
+ "exterior": "house exterior",
75
+ # gardens / outdoor
76
+ "garden": "garden",
77
+ "backyard": "backyard",
78
+ "landscape": "yard and surrounding landscape",
79
+ "patio": "patio and outdoor seating area",
80
+ "deck": "deck",
81
+ "porch": "porch",
82
+ "balcony": "balcony",
83
+ }
84
+
85
+ # Offline fallback: the id lists as of the last mirror refresh. Used only when
86
+ # the live /options fetch fails (e.g. a sandbox with no network) so tool calls
87
+ # still return something sensible.
88
+ FALLBACK = {
89
+ "rooms": [
90
+ "kitchen", "living-room", "bedroom", "bathroom", "dining-room", "home-office",
91
+ "kids-room", "nursery", "basement", "attic", "entryway", "laundry-room", "closet",
92
+ "home-theater", "home-gym", "wine-cellar", "basement-bar", "staircase",
93
+ ],
94
+ "exteriors": ["exterior"],
95
+ "gardens": ["garden", "backyard", "landscape", "patio", "deck", "porch", "balcony"],
96
+ "styles": list(STYLE_LABELS),
97
+ }
98
+
99
+
100
+ class Catalog(dict):
101
+ """Catalog payload: rooms/exteriors/gardens/styles plus a ``live`` flag.
102
+
103
+ A dict subclass keeps attribute access convenient while remaining easy to
104
+ print or serialise.
105
+ """
106
+
107
+ @property
108
+ def rooms(self) -> list[str]:
109
+ return self["rooms"]
110
+
111
+ @property
112
+ def exteriors(self) -> list[str]:
113
+ return self["exteriors"]
114
+
115
+ @property
116
+ def gardens(self) -> list[str]:
117
+ return self["gardens"]
118
+
119
+ @property
120
+ def styles(self) -> list[str]:
121
+ return self["styles"]
122
+
123
+ @property
124
+ def live(self) -> bool:
125
+ return self["live"]
126
+
127
+
128
+ _cache: dict[str, Any] = {"at": 0.0, "data": None}
129
+ _TTL_SECONDS = 10 * 60
130
+
131
+
132
+ def _strings(payload: dict[str, Any], key: str) -> list[str]:
133
+ value = payload.get(key)
134
+ if isinstance(value, list):
135
+ return [item for item in value if isinstance(item, str)]
136
+ return []
137
+
138
+
139
+ async def get_catalog() -> Catalog:
140
+ """Fetch the live taxonomy from /options, cached for 10 min, falling back to
141
+ the bundled lists if the network is unavailable.
142
+ """
143
+ cached = _cache["data"]
144
+ if cached is not None and time.monotonic() - _cache["at"] < _TTL_SECONDS:
145
+ return cached
146
+ try:
147
+ async with httpx.AsyncClient(timeout=8.0, follow_redirects=True) as client:
148
+ response = await client.get(f"{API_BASE}/options")
149
+ response.raise_for_status()
150
+ payload = response.json()
151
+ if not isinstance(payload, dict):
152
+ raise ValueError("/options payload is not an object")
153
+
154
+ def arr(key: str) -> list[str]:
155
+ return _strings(payload, key)
156
+
157
+ data = Catalog(
158
+ rooms=arr("rooms") or FALLBACK["rooms"],
159
+ exteriors=arr("exteriors") or FALLBACK["exteriors"],
160
+ gardens=arr("gardens") or FALLBACK["gardens"],
161
+ styles=arr("styles") or FALLBACK["styles"],
162
+ live=True,
163
+ )
164
+ except Exception:
165
+ data = Catalog(
166
+ rooms=list(FALLBACK["rooms"]),
167
+ exteriors=list(FALLBACK["exteriors"]),
168
+ gardens=list(FALLBACK["gardens"]),
169
+ styles=list(FALLBACK["styles"]),
170
+ live=False,
171
+ )
172
+ _cache["at"] = time.monotonic()
173
+ _cache["data"] = data
174
+ return data
175
+
176
+
177
+ def _title_case(room_id: str) -> str:
178
+ text = room_id.replace("-", " ")
179
+ return re.sub(r"\b\w", lambda match: match.group(0).upper(), text, flags=re.ASCII)
180
+
181
+
182
+ def style_label(style_id: str) -> str:
183
+ return STYLE_LABELS.get(style_id) or _title_case(style_id)
184
+
185
+
186
+ def room_label(room_id: str) -> str:
187
+ return ROOM_LABELS.get(room_id) or _title_case(room_id)
188
+
189
+
190
+ def design_link(room: str, style: str | None = None) -> str:
191
+ """Build the deep link that opens the Roomvana studio with a room (and,
192
+ optionally, a style) pre-selected. Verified against DesignTool.tsx, which
193
+ reads ?room= and ?style= at mount.
194
+ """
195
+ params = {"room": room}
196
+ if style:
197
+ params["style"] = style
198
+ return f"{SITE_BASE}/design?{urlencode(params)}"