FurniOpsDocs

MCP server

Connect Claude, ChatGPT, Cursor or any MCP client to FurniOps. How to sign in, which tools you get, how writes stay safe, and the limits.

FurniOps runs a Model Context Protocol server, so an AI client can read your catalogue and work with it the way your code does with the API. Every tool is a public API route: the same permissions, plan, rate limits and request logs apply, and a tool can never do what the route would refuse.

Endpoint

https://mcp.furniops.com/mcp, over Streamable HTTP. The server speaks MCP 2026-07-28 and serves 2025-era clients too. It holds no session: every request stands alone.

Connect a client

Most clients find everything from the URL alone: the first request is answered with a pointer to FurniOps sign-in, and the client opens it in your browser.

Claude Code

claude mcp add --transport http furniops https://mcp.furniops.com/mcp

Cursor (.cursor/mcp.json) and other clients that take a JSON file:

{
  "mcpServers": {
    "furniops": { "url": "https://mcp.furniops.com/mcp" }
  }
}

Claude, ChatGPT and other apps: add a custom connector and give it the server URL. Sign in when the app asks.

With an API key instead of signing in (servers, CI, agents that run unattended), send it as a bearer token:

claude mcp add --transport http furniops https://mcp.furniops.com/mcp \
  --header "Authorization: Bearer $FURNIOPS_AGENT_KEY"

Signing in

FurniOps sign-in is OAuth 2.1 with PKCE. The server publishes its Protected Resource Metadata (RFC 9728), which names FurniOps as the authorization server and the permissions a token may ask for.

  • You choose the organisation and approve the permissions on a consent screen. Consent is always asked: no MCP client is trusted by default.
  • A token holds at most what an API key may hold, and never more than your role: no client can manage keys, members, the organisation, billing or the agent.
  • Tokens for the MCP server work only there, and API tokens do not work on the MCP server.
  • If a tool needs a permission your token lacks, the server answers 403 with error="insufficient_scope" and the scope it needs. Clients that support step-up ask you to approve it.

Tools

The tools are generated from the API reference, one per route, named after the operation in snake_case (list_products, get_run, update_source). Each takes the route's path and query parameters at the top level, and a write's body as body.

SetWhatWho gets it
ReadEvery GET: products, offers, sources, runs, changes, webhooks, usage, request logs, proposalsEveryone, within their permissions
WriteCreates, runs, merges, webhook endpoint changes, recoveries and testsSigned-in people, and API keys made for an agent

Routes that hand out secrets (creating a webhook endpoint, rotating its secret) or change security are never tools, and neither are deciding proposals or changing the AI agents and the judge. Use the dashboard or the API for those. Some write tools also refuse fields a person must change: update_webhook_endpoint takes no url, so text a model reads can never redirect your events. A call that includes one is refused, not ignored.

Every tool

Generated from the contracts release these docs are built from: the tool, the route it calls and the permission it needs.

ToolCallsSetPermissionHints
check_variantCheck a variant nowPOST /v1/variants/{id}/checksWritefreshness:writenone
complete_uploadComplete an uploadPOST /v1/uploads/{id}/completeWriteruns:writenone
confirm_mappingConfirm a mappingPOST /v1/mappings/{id}/confirmWritesources:writenone
create_exportStart an exportPOST /v1/exportsWriteexports:writenone
create_runStart a runPOST /v1/sources/{id}/runsWriteruns:writenone
create_sourceCreate a sourcePOST /v1/sourcesWritesources:writenone
create_source_probeProbe a vendor URLPOST /v1/source-probesWritesources:writenone
create_uploadStart an uploadPOST /v1/sources/{id}/uploadsWriteruns:writenone
delete_webhook_endpointDelete a webhook endpointDELETE /v1/webhook-endpoints/{id}Writewebhooks:writedestructive, idempotent
dismiss_match_pairDismiss a review pairPOST /v1/match-pairs/{id}/dismissWriteproducts:writenone
get_agreementRead an agreement and where the organisation standsGET /v1/agreements/{kind}Readagreements:readread-only, idempotent
get_billingRead billingGET /v1/billingReadbilling:readread-only, idempotent
get_brandRead the organisation's brandGET /v1/brandReadbrand:readread-only, idempotent
get_brand_claimRead a claimGET /v1/brand/claims/{id}Readbrand:readread-only, idempotent
get_catalogueRead the organisation's own catalogueGET /v1/catalogueReadproducts:readread-only, idempotent
get_exportGet an exportGET /v1/exports/{id}Readexports:readread-only, idempotent
get_judge_settingsRetrieve the AI judge's settingsGET /v1/matching/judge-settingsReadagent:readread-only, idempotent
get_mappingRetrieve a mappingGET /v1/mappings/{id}Readsources:readread-only, idempotent
get_match_pairRetrieve a review pairGET /v1/match-pairs/{id}Readproducts:readread-only, idempotent
get_mediaGet a stored imageGET /v1/media/{id}Readproducts:readread-only, idempotent
get_mergeRetrieve a mergeGET /v1/merges/{id}Readproducts:readread-only, idempotent
get_productGet a productGET /v1/products/{id}Readproducts:readread-only, idempotent
get_proposalRetrieve a proposalGET /v1/proposals/{id}Readagent:readread-only, idempotent
get_publication_runRetrieve a publication runGET /v1/catalogue/publication-runs/{id}Readpublishing:readread-only, idempotent
get_request_logGet a request logGET /v1/request-logs/{id}Readrequest_logs:readread-only, idempotent
get_runRetrieve a runGET /v1/runs/{id}Readruns:readread-only, idempotent
get_sourceRetrieve a sourceGET /v1/sources/{id}Readsources:readread-only, idempotent
get_source_probeRetrieve a source probeGET /v1/source-probes/{id}Readsources:readread-only, idempotent
get_uploadRetrieve an uploadGET /v1/uploads/{id}Readruns:readread-only, idempotent
get_usageRead the month's usageGET /v1/usageReadusage:readread-only, idempotent
get_usage_seriesRead one meter over timeGET /v1/usage/seriesReadusage:readread-only, idempotent
get_vendor_product_statsRead the vendor's most read productsGET /v1/vendor/stats/productsReadanalytics:readread-only, idempotent
get_vendor_profileRead a vendor profileGET /v1/vendor-profiles/{id}Readproducts:readread-only, idempotent
get_vendor_statsRead the vendor's reach and data healthGET /v1/vendor/statsReadanalytics:readread-only, idempotent
get_vendor_stats_seriesRead the vendor's reach week by weekGET /v1/vendor/stats/seriesReadanalytics:readread-only, idempotent
get_webhook_deliveryGet a deliveryGET /v1/webhook-endpoints/{id}/deliveries/{delivery}Readwebhooks:readread-only, idempotent
get_webhook_endpointGet a webhook endpointGET /v1/webhook-endpoints/{id}Readwebhooks:readread-only, idempotent
list_agent_settingsList the agents' settingsGET /v1/agentsReadagent:readread-only, idempotent
list_agreement_acceptancesList the organisation's acceptancesGET /v1/agreement-acceptancesReadagreements:readread-only, idempotent
list_audit_eventsList audit eventsGET /v1/audit-eventsReadaudit:readread-only, idempotent
list_brand_claimsList the organisation's claimsGET /v1/brand/claimsReadbrand:readread-only, idempotent
list_catalogue_imagesList the catalogue's imagesGET /v1/catalogue/imagesReadproducts:readread-only, idempotent
list_catalogue_variantsList the catalogue's variants with price levels and stockGET /v1/catalogue/variantsReadvariants:readread-only, idempotent
list_categoriesList the FurniOps taxonomyGET /v1/categoriesReadsources:readread-only, idempotent
list_changesRead the change feedGET /v1/changesReadevents:readread-only, idempotent
list_exportsList exportsGET /v1/exportsReadexports:readread-only, idempotent
list_inbound_messagesList a source's inbound emailsGET /v1/sources/{id}/inbound-messagesReadsources:readread-only, idempotent
list_invoicesList invoicesGET /v1/billing/invoicesReadbilling:readread-only, idempotent
list_mappingsList a source's mappingsGET /v1/sources/{id}/mappingsReadsources:readread-only, idempotent
list_match_pairsList review pairsGET /v1/match-pairsReadproducts:readread-only, idempotent
list_mergesList mergesGET /v1/mergesReadproducts:readread-only, idempotent
list_offersList offersGET /v1/offersReadproducts:readread-only, idempotent
list_productsList productsGET /v1/productsReadproducts:readread-only, idempotent
list_proposalsList proposalsGET /v1/proposalsReadagent:readread-only, idempotent
list_publication_runsList publication runsGET /v1/catalogue/publication-runsReadpublishing:readread-only, idempotent
list_recent_changesRead the newest changes firstGET /v1/changes/recentReadevents:readread-only, idempotent
list_request_logsList request logsGET /v1/request-logsReadrequest_logs:readread-only, idempotent
list_runsList a source's runsGET /v1/sources/{id}/runsReadruns:readread-only, idempotent
list_source_categoriesList a source's categoriesGET /v1/sources/{id}/categoriesReadsources:readread-only, idempotent
list_source_probesList source probesGET /v1/source-probesReadsources:readread-only, idempotent
list_sourcesList sourcesGET /v1/sourcesReadsources:readread-only, idempotent
list_vendor_profilesSearch the vendor directoryGET /v1/vendor-profilesReadproducts:readread-only, idempotent
list_webhook_deliveriesList an endpoint's deliveriesGET /v1/webhook-endpoints/{id}/deliveriesReadwebhooks:readread-only, idempotent
list_webhook_endpointsList webhook endpointsGET /v1/webhook-endpointsReadwebhooks:readread-only, idempotent
merge_match_pairMerge a review pairPOST /v1/match-pairs/{id}/mergeWriteproducts:writenone
publish_cataloguePublish the catalogue to the networkPOST /v1/catalogue/publishWritepublishing:writenone
push_recordsWrite products to a push sourcePOST /v1/sources/{id}/recordsWriteruns:writenone
record_demandRecord demandPOST /v1/demandWritefreshness:writenone
recover_webhook_deliveriesSend failed deliveries againPOST /v1/webhook-endpoints/{id}/recoverWritewebhooks:writenone
reopen_match_pairReopen a dismissed review pairPOST /v1/match-pairs/{id}/reopenWriteproducts:writenone
replay_runReplay a sourcePOST /v1/sources/{id}/replayWriteruns:writenone
resend_webhook_deliverySend a delivery againPOST /v1/webhook-endpoints/{id}/deliveries/{delivery}/resendWritewebhooks:writenone
save_category_rulesSet a source's category rulesPOST /v1/sources/{id}/categoriesWritesources:writenone
send_webhook_testSend a test eventPOST /v1/webhook-endpoints/{id}/testWritewebhooks:writenone
set_product_visibilityHide products from the network, or list them againPOST /v1/catalogue/visibilityWritepublishing:writenone
test_sourceTest a sourcePOST /v1/sources/{id}/testWritesources:writenone
trigger_test_eventTrigger a sample eventPOST /v1/test/eventsWritewebhooks:writenone
undo_audit_eventUndo an audited changePOST /v1/audit-events/{id}/undoWriteaudit:writenone
undo_mergeUndo a mergePOST /v1/merges/{id}/undoWriteproducts:writenone
update_sourceUpdate a sourcePATCH /v1/sources/{id}Writesources:writeidempotent
update_webhook_endpointUpdate a webhook endpointPATCH /v1/webhook-endpoints/{id}Writewebhooks:writeidempotent
withdraw_catalogueWithdraw the catalogue from the networkPOST /v1/catalogue/withdrawWritepublishing:writedestructive

Every tool states what it risks: readOnlyHint, destructiveHint (only delete_webhook_endpoint today) and idempotentHint. Clients use them to decide when to ask you before a call.

Writes

Which keys may write. An API key is offered write tools only when its purpose is AI agent. Agent keys are always restricted to the permissions you pick, and when your organisation turns on agent key approval, a second member must approve one before it works. A key made for an integration or a third party gets read tools only, so a sync key can never be driven by a model.

Retries do nothing twice. Creating tools take an optional idempotency_key. Without one, the server makes a key for that call. A client that retries with its own key gets the first answer back, as with idempotent requests.

No lost updates. Reading a single resource returns its ETag, as a line of text and in the result's _meta["com.furniops/etag"]. Tools that change a resource (update_source, update_webhook_endpoint) take it as if_match. If someone changed the resource since, the call fails with precondition_failed: read it again and retry. See concurrency.

Results and errors

  • A result is the route's JSON: as text, and as structuredContent when it is an object. Its shape is the route's response in the API reference. Tools carry input schemas only, so the tool list stays small enough for clients that budget tool definitions.
  • A refusal is a tool error naming the problem's title, code and detail (and the fields, for invalid_request), so the model can tell not_found from permission_denied. It is not a protocol error. Codes are explained in errors.
  • Results are data. Product titles, descriptions and notes come from supplier feeds and websites. Every result is marked as untrusted content, and the server tells the model never to follow instructions found in it, or change anything because the content asks.

Limits

  • Plan: MCP is a plan feature. An organisation whose plan does not include it is answered 403 feature_unavailable.
  • Rate: each signed-in caller (key or person, per mode) has its own budget for MCP requests: 50 a second, bursts of 200. Each tool call also takes your organisation's API rate budget, as the route it is. Callers never share a budget because they share an address, so hosted clients are not slowed by each other.
  • Refused credentials: an address that sends more than 20 refused credentials a second (bursts of 200) is answered 429 for a moment, before anything is verified. Refresh expired tokens instead of retrying them.
  • Quota: every tool call counts one MCP call in usage, and not an API call. Listing tools is free. When the month's quota is spent, tool calls return quota_exceeded until the next month; test mode is never capped. Counts reach usage within seconds.
  • Requests are at most 1 MiB.

Test mode

A test API key reaches only your sandbox, as on the API. A signed-in client works in live mode; send FurniOps-Mode: test with every request to use the sandbox. See test mode.

In the dashboard

Developers › MCP shows the server URL, a ready-made setup for each client (signing in with OAuth, or an agent API key), what a client may do (read tools, write tools, and what is never a tool), and the limits. API keys for an agent are made under API keys.

On this page