Skip to content

MCP server

A remote Model Context Protocol server, so any MCP-capable agent can read and write Sunny projects, rooms, catalog, and quotes over the network.

POST https://agents.sunnysdk.com/mcp — stateless Streamable HTTP: one JSON-RPC request per call, no session id. Responses come back as plain JSON, not an SSE stream. GET and DELETE return 405.

Required headers
Content-Type: application/json
Accept: application/json, text/event-stream
Authorization: Bearer <access token>

No tool spends model tokens. There is no arrange, plan-floor, or AI preview render over MCP. Every tool is a read, a deterministic render, or a write that changes stored data.

  1. Find the authorization server. GET /.well-known/oauth-protected-resource on agents.sunnysdk.com returns RFC 9728 metadata naming the authorization server and its scopes. A 401 from /mcp carries the same URL in a WWW-Authenticate header, so a compliant client can find it with no out-of-band config.

    GET https://agents.sunnysdk.com/.well-known/oauth-protected-resource
    {
    "resource": "https://agents.sunnysdk.com/mcp",
    "authorization_servers": [
    "https://auth.sunnysdk.com/api/auth"
    ],
    "bearer_methods_supported": [
    "header"
    ],
    "scopes_supported": [
    "openid",
    "profile",
    "email",
    "offline_access",
    "projects:read",
    "projects:write",
    "quotes:read",
    "quotes:write",
    "quote_costs:read",
    "orders:write",
    "arrange",
    "workflows:run",
    "workflows:read",
    "catalog:read",
    "catalog:write",
    "docs:read",
    "embeds:write",
    "embeds:read",
    "webhooks:manage",
    "org:admin",
    "audit:read",
    "admin"
    ]
    }

    A server card for pre-connection discovery is also served at /.well-known/mcp/server-card.json.

  2. Register a client. Preferred: host a JSON Client ID Metadata Document at a public HTTPS URL (MCP 2026-07-28 profile — the document needs client_name and redirect_uris) and use that URL as your client_id directly; no registration call needed. See Registering a client for the document shape. RFC 7591 Dynamic Client Registration at POST https://auth.sunnysdk.com/api/auth/oauth2/register still works as a fallback for a client that can’t serve a metadata document.

  3. Authorize. OAuth 2.1 authorization code with PKCE, with resource=https://agents.sunnysdk.com/mcp. The user consents on Sunny every time; a grant is never silently re-issued. Ask for only the scopes you need — see OAuth & scopes for what each one grants.

  4. Call the endpoint. Every request carries the Bearer token. The MCP server accepts OAuth Bearer tokens only — no API keys, no cookies. A missing, invalid, or expired token always gets a 401.

Scope is checked on every tool call, not once at connection time. Everything is scoped to the user’s active workspace: an id belonging to another workspace reads as “Not found”, so another workspace’s data and a typo are indistinguishable by design.

Clients that support MCP Apps render some results as interactive cards (catalog search, a product card, a project, a plan, a live room). Every tool also returns a text result for clients that don’t.

Tool Scope What it does
list_projects projects:read Projects visible to the user’s active workspace.
get_project projects:read One project by id, with its floors and spaces.
get_floor projects:read One floor by id.
get_space projects:read One space (room) by id, plus its arrangement history, newest first. With projects:write, also the room as an open studio tab has it right now (liveRoom).
export_layout projects:read A space’s current arrangement as a sunny-layout document.
import_layout projects:write Import a sunny-layout document into a space as a new saved version. Unknown catalog ids are dropped and reported per item.
render_plan projects:read A deterministic top-down plan as a PNG plus a text legend: one space, every room on a floor, or a sunny-layout document rendered without saving it.
render_view projects:read A 3D view of a space as an image (iso or top). A first render of a layout can take 15–40 seconds; unchanged layouts return quickly.
show_room projects:read A live card for one room, in 2D or 3D, that follows edits as they happen. Call it once before editing. Where the app allows, expand it to full screen to keep it in view. With only projects:read it shows the room as last saved.
place_item projects:write Live edit: add one catalog piece at a position in meters.
move_item projects:write Live edit: move or turn a piece by id. Only the fields you pass change.
remove_item projects:write Live edit: remove a piece. The person can undo it from the studio.

Live edits (place_item, move_item, remove_item) change a room while it is open in the Sunny studio with live editing turned on (Connect AI in the studio). If no studio tab has the room connected, the call returns isError: true saying so, and nothing changes. Take the room id from get_project or get_floor, and piece ids from get_space’s liveRoom. When the room is open this way, get_space, render_plan, and render_view also see its unsaved edits.

Tool Scope What it does
search_catalog catalog:read Free-text and category search over the catalog. Returns text for the agent and shows the person nothing.
inspect_catalog_item catalog:read Full detail for one catalog id: footprint, finishes, sizes, and configuration options.
show_catalog_item catalog:read One catalog item as an interactive product card (3D view, finishes, sizes, configured price).
show_catalog_items catalog:read Up to 12 chosen catalog items as a grid of product cards, shown once with the agent’s final picks.
get_reference_photos catalog:read Real product photos for one or more catalog ids, so a vision-capable agent can compare them against a render or a user’s photo.

Dealer-only items appear only to a workspace with an active owner or dealer relationship with that item’s manufacturer; to anyone else they read as unknown ids.

Tool Scope What it does
search_docs docs:read Search product documentation — family composition rules, dimension tables, named assemblies, an item’s attach points and configuration — by free text.
get_docs docs:read Fetch one product doc by id (family:<familyId> or item:<catalogId>): its summary and section list, or one section’s full text.

These are about furniture products already found with search_catalog, not about this developer documentation. docs:read is elevated: it is granted per client, never by default.

Tool Scope What it does
search_customers quotes:read Find customers in the workspace by name or email.
get_quote quotes:read One quote with its customer, lines, totals, and Present link. Dealer cost and margin appear only with quote_costs:read.
preflight_order quotes:read Check an order (SIF or OFDA XML text, or the order a quote exports) against the manufacturer’s price book: part numbers, options, required choices, and prices. Reports only; never edits.
list_discount_tables quote_costs:read The workspace’s discount tables and their revisions, so a quote can be priced against a named one.
save_customer quotes:read and quotes:write Create or update a customer. Creating with an email that already exists returns the existing customer instead of a duplicate.
save_quote quotes:read and quotes:write Create or update a quote, optionally with a batch of line changes. Line changes run in order and are not transactional: a failure reports what was applied.
present_quote quotes:read and quotes:write Publish the quote’s customer-facing Present link and return its URL. Idempotent. It does not send email.
quote_from_spaces quotes:write Create a quote seeded from several rooms of one project, one group of lines per room.
add_room_to_quote quotes:write Append one room’s lines to an existing quote, or re-seed a room already on it.

quotes:write and quote_costs:read are elevated scopes. Money in a quote result is in integer minor units (cents for USD), the same as the REST /v1 API.

Tool Scope What it does
manage_item_embeds embeds:write Mint, list, update, or revoke single-item embed tokens.
item_embed_stats embeds:read or embeds:write 30-day daily view, selection, and open counters for one embed token. Unknown tokens read as all-zero, never an error.
manufacturer_embed_stats embeds:read or embeds:write 30-day roll-up of embed activity across every workspace for one manufacturer. Requires the manufacturer’s owner relationship; a dealer gets an error.

Embed tools are limited to items from manufacturers your workspace works with, as the manufacturer or as a dealer. A dealer sees only its own workspace’s tokens.

POST /mcp — token with projects:read
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "export_layout",
"arguments": { "spaceId": "spc_k3m9x1q4z8f2" }
}
}
200 OK
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{\"format\":\"sunny-layout\",\"version\":1,...}"
}
]
}
}

A tool-level failure — wrong scope, unknown id, an invalid document — comes back as a JSON-RPC success envelope whose result carries isError: true and a readable message. That’s the MCP convention for business errors, and it’s distinct from a protocol error or the connection-level 401 for a bad token.

Missing scope
{
"content": [
{
"type": "text",
"text": "insufficient_scope: this tool requires the \"projects:write\" scope, which the presented token does not grant."
}
],
"isError": true
}

These tools share a budget of 5 calls per 60 seconds per OAuth client and user: import_layout, render_plan, render_view, manage_item_embeds, save_customer, save_quote, present_quote, quote_from_spaces, and add_room_to_quote. It is the same budget as the REST API’s cost endpoints, so calls on either surface count against it.

Live edits (place_item, move_item, remove_item) have their own limit of 120 calls per 60 seconds per OAuth client, user, and room.

Tools that read or write through the REST API on your behalf (catalog search, the quote tools, live edits, render_view) also count toward the REST API’s per-user limits.

Over a limit, the tool returns isError: true with a rate_limited: message naming the retry window, rather than an HTTP 429.