An MCP-first interior design agent. Decora furnishes a room from a real catalog, writes the job to a design project, and stops for human approval before anything is committed.
This is not a chat wrapper. The model does not invent SKUs or keep the spec in conversation history. The world lives on an MCP server; the Decor app is a host that discovers tools, reads resources, and lets you approve the cart.
Built against the Agentic AI Foundation MCP standard. Why each slice exists is in docs/aaif-learning.md.
You describe a room, budget, and constraints. Decora searches seeded inventory, updates project://{context_key}, and calls request_approval when the spec covers the room and the budget holds. You approve or reject in the UI. Draft items stay draft until you do.
Example jobs:
- "12x14 living room, $2000, keep grandma's credenza"
- "What paint from the catalog works with dark oak floors?"
- "Add a sofa under $1200 and pause for approval"
Old specialist prompts (style_advisor, room_planner, trend_spotter) are still in the tree. /api/chat does not use them.
Host (FastAPI + web/)
chat UI + project panel
harness: observe → tools/list → model → tools/call → stop for approval
MCP client
│
│ JSON-RPC (in-process Client, or stdio for Inspector)
▼
Server (mcp_servers/decor_design.py) — no Claude
tools: search_catalog, update_project, request_approval
resources: project://{context_key}, catalog://sku/{sku}
prompts: plan_room (user-invoked)
│
▼
World: seeded catalog + in-memory project store
| Primitive | Who controls it | In this repo |
|---|---|---|
| Tools | Model | Search inventory, mutate the project, ask to commit |
| Resources | Host / app | Read the project and a SKU without a tool call |
| Prompts | User | plan_room — room, budget, keep, avoid |
The model cannot call approve. That is a host route on purpose.
decor-agent/
├── mcp_servers/decor_design.py # MCP environment (tools, resources, prompts)
├── app/
│ ├── catalog.py # Seeded SKUs — if it is not here, it does not exist
│ ├── project.py # Brief, rooms, spec list, budget, approval
│ ├── store.py # In-memory projects keyed by context_key
│ ├── harness.py # Host loop: discover tools, call MCP, read project://
│ ├── prompts.py # Decora system prompt (not plan_room)
│ ├── graph.py # Legacy specialist graph — unused by /api/chat
│ └── tools/ # Legacy LLM “tools” — unused by /api/chat
├── server.py # FastAPI: /api/chat, /api/project, approve, reject
├── web/ # Chat + project panel
├── test_catalog.py
├── test_store.py
├── test_mcp_catalog.py
├── test_mcp_project.py
├── test_harness.py
├── test_agent.py # Live LLM e2e (needs ANTHROPIC_API_KEY)
├── docs/aaif-learning.md
└── .env.example
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # then add ANTHROPIC_API_KEY
python server.py # http://localhost:8000Open the chat UI and give Decora a room. The right-hand panel is project://, not a second transcript.
curl -X POST http://localhost:8000/api/chat \
-H 'Content-Type: application/json' \
-d '{"message": "Plan a 12x14 living room with a $2000 budget"}'Host-only (the model cannot hit these):
curl 'http://localhost:8000/api/project?context_key=demo'
curl -X POST http://localhost:8000/api/project/approve \
-H 'Content-Type: application/json' \
-d '{"context_key": "demo", "kind": "spec"}'The MCP server is usable without the chat app:
python mcp_servers/decor_design.pyOr open MCP Inspector:
npx @modelcontextprotocol/inspector python mcp_servers/decor_design.pyThen tools/list, resources/read on project://demo or catalog://sku/ART-SOFA-721, and prompts/get plan_room.
No API key needed for the protocol and store tests:
python test_catalog.py test_store.py test_mcp_catalog.py test_mcp_project.py test_harness.pyLive routing against Claude:
LOG_LEVEL=WARNING python test_agent.py| Variable | Default | Purpose |
|---|---|---|
ANTHROPIC_API_KEY |
required for chat | Claude API key (not required for Inspector or MCP unit tests) |
ANTHROPIC_WORKSPACE_ID |
"" |
Required if the key is not scoped to one Anthropic workspace |
LD_SDK_KEY |
"" |
LaunchDarkly server SDK key (unused for the MCP loop) |
LOG_LEVEL |
INFO |
structlog level |
ENVIRONMENT |
development |
Console vs JSON logs |
Unknown .env keys are ignored so leftover Temporal-era variables do not crash settings.
AAIF build order after MCP + this host:
- Point goose at
decor-design(manual check — if goose cannot furnish a room, the server is not done) AGENTS.md— stop conditions and consent, once the agent exists- A2A — e.g. a retailer agent for stock
- agentgateway — only when there is more than one thing to front
- LaunchDarkly — gate work that is already real
Temporal (API World durable-workflow demo) lives on temporal-api-world and durable-workflows, not on main.
Python 3.12 · MCP Python SDK (MCPServer) · Anthropic Claude Sonnet 5 · FastAPI · Pydantic · structlog
