← Flowerbed

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

  1. In Claude, open Settings → Connectors and add a custom connector.
  2. Name it Flowerbed and paste https://mcp.flowerbed.app.
  3. 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 POST to https://mcp.flowerbed.app (root path only: https://mcp.flowerbed.app/mcp returns 404). Responses are always application/json, never SSE, and an Accept header is not required. There are no sessions: Mcp-Session-Id and MCP-Protocol-Version headers are accepted but not needed.
  • GET returns 405: the server does not open a stream.
  • Notifications get 202 with an empty body. JSON-RPC batches are accepted.
  • The server echoes the protocol version requested in initialize if it supports it, otherwise it answers 2025-11-25.
  • Capabilities: tools only (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>}. Parse text as JSON.
  • Tool failures come back as a normal result with result.isError: true and a text message.
  • Argument limits and bounds are in each tool's inputSchema from tools/list.

Tools

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [{ "type": "text", "text": "{\"refs\": [...]}" }]
  }
}

tools/list returns the full schemas. Current tools:

AreaTools
Refslist_refs, get_ref, search_refs, save_ref, delete_ref
Bedslist_beds, get_bed, search_beds, create_bed, update_bed, delete_bed, add_refs_to_bed, remove_refs_from_bed, share_bed
Aestheticsrecommend_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"
WhatURL
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
Issuerhttps://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://, or http://localhost / http://127.0.0.1 on any port, and you can send at most 10. Anything else is rejected with 400 invalid_redirect_uri.
  • client_name is at most 100 characters and defaults to MCP 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
ParameterNotes
client_idRequired.
redirect_uriRequired. Must be one of the registered URIs.
code_challengeRequired.
response_typeDefaults to code, the only supported value.
code_challenge_methodDefaults to S256.
scopeDefaults to mcp.
stateOptional but recommended. Echoed back to your redirect URI.
resourceAccepted (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_uri with error=access_denied (plus state). Clients should handle this.
  • Bad requests: bad or missing parameters, an untrusted or unregistered redirect_uri and an unsupported response_type are 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 answers 401 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_grant with refresh_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 200 with an empty body, including for unknown tokens and for access tokens.
  • A missing token gives 400 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.

WhereResponseMeaning and fix
MCP401 {"error":"missing bearer token"}No token sent. Run the OAuth flow. The WWW-Authenticate header is present.
MCP401 {"error":"invalid or expired token"}Access token expired or invalid. Refresh it. The same WWW-Authenticate header is present.
MCP403 plan_requiredThe account plan does not include the connector. All accounts currently have it.
MCP429 rate_limited120 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.
MCP405 on GETExpected. The server has no SSE stream. Use POST.
MCPJSON-RPC -32601Unknown method.
MCPJSON-RPC -32700The body is not valid JSON.
Token400 unsupported_grant_typeOnly authorization_code and refresh_token are supported.
Token400 invalid_requestMissing code, code_verifier, redirect_uri or client_id (or refresh_token).
Tokeninvalid_grant: code_expiredThe code is older than 10 minutes. Authorize again.
Tokeninvalid_grant: code_replayedThe code was used twice. That client's tokens are revoked. Authorize again.
Tokeninvalid_grant: client_mismatch, redirect_uri_mismatch, pkce_failedThe exchange parameters differ from the authorize request.
Tokeninvalid_grant: refresh_token_expiredThe refresh token expired or was revoked. Authorize again.
Tokeninvalid_grant: refresh_token_replayedAn old refresh token was reused. All tokens are revoked. Authorize again and serialize refreshes.
Tokeninvalid_grant (no description)Unknown code or refresh token.
AuthorizeUntrusted redirect_uriThe redirect must be https, or http://localhost / 127.0.0.1.
Authorizeredirect_uri not registeredThe redirect_uri is not one of the URIs sent at registration.
Register400 invalid_redirect_uriA 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.