Connect an MCP client
Developer docs
What it is
Flowerbed runs a remote Model Context Protocol server. Any MCP client that supports remote servers with OAuth can read and write your own refs and beds through it: list and search your library, save new links, create and update beds, add or remove refs, share a bed, and browse aesthetics. You approve the connection once by signing in to your Flowerbed account.
Quick facts
- Server URL
https://mcp.flowerbed.app- Transport
- Streamable HTTP, stateless, JSON responses
- Protocol
2024-11-05,2025-03-26,2025-06-18,2025-11-25- Auth
- OAuth 2.1 style, public client, PKCE (S256), dynamic client registration
- Scope
mcp
Using Claude
- In Claude, open Settings → Connectors and add a custom connector.
- Name it Flowerbed and paste
https://mcp.flowerbed.app. - Connect, then sign in with your Flowerbed account.
Using any other MCP client
The only thing a client needs is the server URL. It discovers the authorization server, registers itself and runs the sign-in on its own. A typical configuration looks like this, though the exact file and keys depend on your client:
{
"mcpServers": {
"flowerbed": {
"url": "https://mcp.flowerbed.app"
}
}
}Clients that support remote MCP servers with OAuth handle the rest. If your client only supports local (stdio) servers, it cannot connect to Flowerbed directly. The legacy HTTP+SSE transport is not supported.
Protocol details
- Every JSON-RPC message is a
POSTtohttps://mcp.flowerbed.app(root path only:https://mcp.flowerbed.app/mcpreturns404). Responses are alwaysapplication/json, never SSE, and anAcceptheader is not required. There are no sessions:Mcp-Session-IdandMCP-Protocol-Versionheaders are accepted but not needed. GETreturns405: the server does not open a stream.- Notifications get
202with an empty body. JSON-RPC batches are accepted. - The server echoes the protocol version requested in
initializeif it supports it, otherwise it answers2025-11-25. - Capabilities:
toolsonly (listChanged: false). No resources, prompts, sampling or elicitation. - Methods:
initialize,notifications/initialized,ping,tools/list,tools/call. - Tool results:
result.content[0]is{type: "text", text: <JSON string>}. Parsetextas JSON. - Tool failures come back as a normal result with
result.isError: trueand a text message. - Argument limits and bounds are in each tool's
inputSchemafromtools/list.
Tools
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{ "type": "text", "text": "{\"refs\": [...]}" }]
}
}tools/list returns the full schemas. Current tools:
| Area | Tools |
|---|---|
| Refs | list_refs, get_ref, search_refs, save_ref, delete_ref |
| Beds | list_beds, get_bed, search_beds, create_bed, update_bed, delete_bed, add_refs_to_bed, remove_refs_from_bed, share_bed |
| Aesthetics | recommend_aesthetics, get_aesthetic, list_refs_by_aesthetic |
Authorization step by step
Spec-compliant clients do all of this automatically. It is documented here for client authors.
1. Discovery
A request to the MCP URL without a token returns 401 with a pointer to the resource metadata. Both a missing and an invalid token carry this same header:
HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer error="invalid_token", resource_metadata="https://flowerbed.app/.well-known/oauth-protected-resource"
| What | URL |
|---|---|
| Protected resource metadata (RFC 9728) | https://flowerbed.app/.well-known/oauth-protected-resource |
| Authorization server metadata (RFC 8414) | https://flowerbed.app/.well-known/oauth-authorization-server |
| Issuer | https://flowerbed.app |
The resource metadata lists https://flowerbed.app as the authorization server and mcp as the supported scope. It is also served at https://mcp.flowerbed.app/.well-known/oauth-protected-resource. OpenID Connect discovery is not provided.
2. Register the client
Dynamic client registration (RFC 7591). Send a name and your redirect URIs. The server answers 201 Created (RFC 7591) and echoes client_id, client_id_issued_at, token_endpoint_auth_method, grant_types, response_types, redirect_uris and client_name. There is no secret: clients are public (token_endpoint_auth_method: "none").
curl -X POST https://flowerbed.app/oauth/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "My MCP client",
"redirect_uris": ["http://localhost:8080/callback"]
}'- Redirect URIs must be
https://, orhttp://localhost/http://127.0.0.1on any port, and you can send at most 10. Anything else is rejected with400 invalid_redirect_uri. client_nameis at most 100 characters and defaults toMCP Client.- The registration is stored. The name and redirect URIs are shown to the user on the consent screen, and authorize accepts only the registered redirect URIs.
- Client secrets, confidential clients and Client ID Metadata Documents are not supported.
3. Authorize with PKCE
Send the user's browser to the authorize endpoint. Use S256. The metadata also advertises plain, but do not use it.
https://flowerbed.app/oauth/authorize ?response_type=code &client_id=<client_id> &redirect_uri=http://localhost:8080/callback &code_challenge=<S256 challenge> &code_challenge_method=S256 &state=<random state> &scope=mcp
| Parameter | Notes |
|---|---|
client_id | Required. |
redirect_uri | Required. Must be one of the registered URIs. |
code_challenge | Required. |
response_type | Defaults to code, the only supported value. |
code_challenge_method | Defaults to S256. |
scope | Defaults to mcp. |
state | Optional but recommended. Echoed back to your redirect URI. |
resource | Accepted (RFC 8707) and ignored. |
A user who is not signed in is sent to the flowerbed.app login (email and password, Google or Apple) and then back to the consent screen. When the user approves, Flowerbed redirects to your redirect_uri with code and state. The code is valid for 10 minutes and is consumed by the first successful exchange.
- Cancel: if the user clicks Cancel, the browser goes to your
redirect_uriwitherror=access_denied(plusstate). Clients should handle this. - Bad requests: bad or missing parameters, an untrusted or unregistered
redirect_uriand an unsupportedresponse_typeare shown to the user as an HTML page on flowerbed.app. There is no redirect back to your client, so validate your request before you open the browser.
4. Exchange the code for tokens
POST https://flowerbed.app/oauth/token, form-encoded or JSON. The redirect_uri must equal the one used at authorize (trailing slashes are ignored). A failed exchange, for example a redirect_urimismatch, does not consume the code. Presenting a code that was already redeemed revokes that client's tokens (code_replayed).
curl -X POST https://flowerbed.app/oauth/token \ -d grant_type=authorization_code \ -d code=<code> \ -d code_verifier=<verifier> \ -d redirect_uri=http://localhost:8080/callback \ -d client_id=<client_id>
{
"access_token": "...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "...",
"scope": "mcp"
}5. Call the server
Send the access token as a bearer token on every request.
curl -X POST https://mcp.flowerbed.app \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Staying connected
- Access token: valid for 1 hour (
expires_in: 3600). When the MCP server answers401 invalid_token, refresh. - Refresh token: valid for 90 days from when it was issued. Every refresh issues a new 90-day token, so a client that refreshes at least once every 90 days stays connected indefinitely.
- Rotation: a refresh returns a new access token and a new refresh token. Store the new refresh token immediately: the old one is dead.
- Single use: refresh tokens can be used once. Presenting an already-used one is treated as theft. Flowerbed revokes all tokens for that user and client and returns
invalid_grantwithrefresh_token_replayed, and the client has to run the authorization flow again. - Serialize refreshes. Never refresh concurrently with the same token. Keep one refresh in flight per connection and persist the new token before using it.
curl -X POST https://flowerbed.app/oauth/token \ -d grant_type=refresh_token \ -d refresh_token=<refresh_token>
client_id is optional on refresh and ignored: the refresh token identifies the client.
Persist more than the tokens. Store the token endpoint (or the authorization server metadata) and the absolute expiry time next to the tokens, so a client that restarts can refresh without a new login. Clients that only kept the tokens can use the MCP 2025-03-26 fallback endpoints, which also work: https://mcp.flowerbed.app/token and https://mcp.flowerbed.app/register serve the same handlers, and https://mcp.flowerbed.app/authorize redirects to the authorize page.
What ends access
- The refresh token goes unused for 90 days.
- An old refresh token is replayed.
- An authorization code is replayed (that client's tokens are revoked).
- The user disconnects the app in Settings → Connected apps, or the client calls
/oauth/revoke(see below). - The account is deleted.
Revoking a token
POST https://flowerbed.app/oauth/revoke (RFC 7009), form-encoded or JSON, with the refresh token as token. No client authentication is needed. token_type_hint and client_id are optional and ignored. The authorization server metadata advertises revocation_endpoint and revocation_endpoint_auth_methods_supported: ["none"].
curl -X POST https://flowerbed.app/oauth/revoke \ -d token=<refresh_token>
- The answer is always
200with an empty body, including for unknown tokens and for access tokens. - A missing
tokengives400 invalid_request. - Revoking a refresh token ends the whole chain for that user and client.
- Access tokens cannot be revoked. They expire within 1 hour.
Access tokens are stateless. After a revocation, an access token that was already issued keeps working until it expires, which is 1 hour at most.
Errors and fixes
The 401, 403, 405 and 429 answers from the MCP server are plain HTTP JSON responses, not JSON-RPC errors. The 401 variants differ only in the JSON body; both carry the same WWW-Authenticate header.
| Where | Response | Meaning and fix |
|---|---|---|
| MCP | 401 {"error":"missing bearer token"} | No token sent. Run the OAuth flow. The WWW-Authenticate header is present. |
| MCP | 401 {"error":"invalid or expired token"} | Access token expired or invalid. Refresh it. The same WWW-Authenticate header is present. |
| MCP | 403 plan_required | The account plan does not include the connector. All accounts currently have it. |
| MCP | 429 rate_limited | 120 requests per minute and 1500 per hour per user. The body has retry_after_seconds and the response has a Retry-After header. Wait that long. |
| MCP | 405 on GET | Expected. The server has no SSE stream. Use POST. |
| MCP | JSON-RPC -32601 | Unknown method. |
| MCP | JSON-RPC -32700 | The body is not valid JSON. |
| Token | 400 unsupported_grant_type | Only authorization_code and refresh_token are supported. |
| Token | 400 invalid_request | Missing code, code_verifier, redirect_uri or client_id (or refresh_token). |
| Token | invalid_grant: code_expired | The code is older than 10 minutes. Authorize again. |
| Token | invalid_grant: code_replayed | The code was used twice. That client's tokens are revoked. Authorize again. |
| Token | invalid_grant: client_mismatch, redirect_uri_mismatch, pkce_failed | The exchange parameters differ from the authorize request. |
| Token | invalid_grant: refresh_token_expired | The refresh token expired or was revoked. Authorize again. |
| Token | invalid_grant: refresh_token_replayed | An old refresh token was reused. All tokens are revoked. Authorize again and serialize refreshes. |
| Token | invalid_grant (no description) | Unknown code or refresh token. |
| Authorize | Untrusted redirect_uri | The redirect must be https, or http://localhost / 127.0.0.1. |
| Authorize | redirect_uri not registered | The redirect_uri is not one of the URIs sent at registration. |
| Register | 400 invalid_redirect_uri | A redirect URI is not https or localhost. |
Security
The mcp scope gives full read and write access to your own refs and beds through the tools above. Access tokens are user tokens that are not bound to the MCP server, so treat them like a credential for your Flowerbed data: keep them out of logs and shared configs, and only connect clients you trust. You can disconnect any app at any time in Settings → Connected apps.
Contact
Building a client and something does not behave as described here? Write to hello@flowerbed.app.