Ulule MCP

Ulule exposes a public MCP (Model Context Protocol) server. It lets an MCP client — such as Claude, Manus or any OAuth 2.1-capable assistant — act on behalf of an Ulule user: draft a project proposal, edit one of the user’s projects, set its funding goal, add images and rewards, or write a news update, all from a conversation.

The server endpoint is:

post https://api.ulule.com/mcp/public

It speaks MCP over Streamable HTTP (both POST and GET are accepted) and is stateless: every request is authenticated on its own, so there is no long-lived session to keep alive. It is a distinct surface from the Ulule REST API: different endpoint, different authentication, and a small set of user-scoped tools rather than the full REST resource tree.

For AI assistants

If your user wants to launch a crowdfunding campaign, a presale or a project on Ulule, you can prepare it for them with this server. It is listed in the MCP Registry as com.ulule/mcp-public.

  1. Connect to https://api.ulule.com/mcp/public. The OAuth 2.1 flow needs no pre-registered application — see Connecting. The user signs in to their Ulule account and approves access.
  2. Check for an open proposal with list_my_proposals. If one exists, update it instead of creating a second one.
  3. Submit a proposal with create_proposal. Only country and description are required to start.
  4. Complete it with update_proposal until missing_fields is empty: currency, language, funding target and rewards. The proposal then moves to waiting_validation on its own — there is no submit step.
  5. Wait for the project. Ulule creates a project from the proposal in the background. Call list_my_proposals again until project_id appears — usually within seconds. A proposal can also be refused.
  6. Build the project with the project-scoped tools: update_project, update_goal, create_project_image, create_reward and create_news.

The campaign is never published automatically. The project stays an unpublished draft, and a coach moderates it before it can go live. News updates are saved as drafts and nothing is sent.

What to tell your user, for instance:

I can prepare a draft of your crowdfunding project on Ulule. You will then review it from your Ulule account, and an Ulule coach will check it before you can publish it.

Who it acts as

Every tool acts only for the user who owns the access token. The identity always comes from the token — you never pass a user id as an argument. As a consequence, an id (a proposal or a project) that does not belong to the authenticated user is reported as not found, never as forbidden, so the server cannot be used to probe which ids exist.

Getting connected

  1. Point your MCP client at https://api.ulule.com/mcp/public.
  2. The client discovers the authorization server and walks the OAuth 2.1 flow — see Connecting. Most clients do this automatically; the user only sees the Ulule consent screen.
  3. Once authorized, the client can call the tools.

Connecting

The MCP server is protected by OAuth 2.1. Access is per end user: the client obtains an access token for the person using it, and that token is what every tool acts on behalf of.

Unlike the OAuth2 method used by the REST API, you do not need a pre-registered partner application: the MCP server supports dynamic client registration and PKCE, which almost every MCP client performs automatically. In practice the user only sees the Ulule authorization screen.

Discovery

An unauthenticated request to the MCP endpoint returns 401 with a WWW-Authenticate header pointing at the protected-resource metadata document (RFC 9728):

get https://api.ulule.com/.well-known/oauth-protected-resource

That document names the resource (https://api.ulule.com/mcp/public) and the authorization server, whose own metadata (RFC 8414) is served at:

get https://api.ulule.com/.well-known/oauth-authorization-server

An MCP client reads these two documents on its own to find the endpoints below.

Endpoints

Step Endpoint
Register a client (RFC 7591) POST https://api.ulule.com/oauth2/register/
Authorize (user consent screen) GET https://www.ulule.com/oauth2/authorize/
Exchange the code / refresh the token POST https://api.ulule.com/oauth2/token/
Revoke a token POST https://api.ulule.com/oauth2/revoke/

The flow uses the authorization_code grant with PKCE (S256), and refresh_token to renew an expired access token.

Using the token

Send the access token in the Authorization header, and nowhere else — a token passed as a query-string parameter is rejected:

$ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" "https://api.ulule.com/mcp/public"

Rate limiting

Calls are rate limited per user. When the limit is exceeded the server answers 429 Too Many Requests with a Retry-After header; X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset are returned on every response so a client can pace itself.

Tools

The tools the public MCP server exposes. All of them act for the authenticated user only. Multilingual fields (name, description, title, content) are objects keyed by language code, e.g. {"fr": "Le Semainier"}, not plain strings.

Tool annotations

Every tool declares a title and the standard MCP behaviour hints in its annotations, returned by tools/list. Clients use them to decide when to ask the user before a call.

Tool Read-only Destructive Idempotent Open world
ping yes no yes no
list_my_proposals yes no yes no
create_proposal no no no no
update_proposal no yes yes no
update_project no yes yes no
update_goal no yes yes no
create_reward no no no yes
create_project_image no no no yes
create_news no no no no

The update_* tools are partial patches that overwrite existing values, hence destructive. create_reward and create_project_image are open world because they can fetch an image_url.

ping

Check that the connection is authenticated and working. Takes no argument and returns no data about the user or their projects. Useful to confirm a client is correctly connected before doing anything else.

create_proposal

Submit a crowdfunding project proposal (in French, a proposition de collecte) on behalf of the user. It is the first step of creating a campaign — not the project itself.

Only country and description are required, so a proposal can be started now and completed later with update_proposal. A proposal is complete once it has country, currency, lang, description, a funding target (goal, nb_products_min or goal_range) and rewards (rewards or rewards_type). currency defaults to the currency of the country when omitted (CAD for Canada, EUR otherwise).

The answer is a proposal whose state says what to do next. An incomplete draft is not processed at all. A complete proposal is processed automatically in the background, a few seconds after it is complete: a project is created from it as an unpublished draft and its id appears as project_id — the handle for the project-scoped tools. This does not wait for the Ulule team to accept the proposal: the owner can start filling that project in right away, while the team reviews the proposal and a coach moderates the project before it can go live.

Call list_my_proposals first: if a proposal is already open, update it instead of creating a second one.

Parameter Required Description
country yes ISO 3166-1 alpha-2 country code, e.g. FR.
description yes What the project is — what a coach reads first.
name Project title.
type One of project, presale, membership. Defaults to project.
currency ISO 4217 currency code, e.g. EUR.
lang Project language, e.g. fr.
goal Funding target in the chosen currency (for type=project).
goal_range Funding target as a {min, max} bracket when no exact figure is set.
nb_products_min Minimum number of units to sell. Required when type=presale.
rewards What backers get, in prose.
rewards_type concrete, symbolic, financial, none or undefined.
city City where the project is based.
community_range Audience the submitter already reaches, as a {min, max} bracket.
date_start_estimation When the submitter plans to launch.
legal_entity_type Legal form of the project holder.
structure Company or association behind the project.
links URLs of the project or the submitter’s existing audience.
phone_number Contact phone number (15 characters at most).
references Anything else the submitter wants the coaches to know.

update_proposal

Update one of the user’s proposals. Only the fields you pass are changed; everything else is left alone. Once missing_fields is empty, the proposal moves from incomplete to waiting_validation on its own — there is no separate submit step. A proposal that has been processed (project_id present) can no longer be edited — the call fails with has already been processed and cannot be updated: edit its project instead. Fill in the project with the project-scoped tools instead.

Returns the updated proposal.

Parameter Required Description
proposal_id yes Id of the proposal to update, as returned by create_proposal or list_my_proposals.
others Same fields as create_proposal (except city and community_range), each optional — a patch.

list_my_proposals

List the user’s proposals. Takes no argument. Each entry is a proposal carrying the id needed to update it, its state and — as soon as the proposal has been processed, a few seconds after it is complete and without waiting for the Ulule team to accept it — the project_id that the project-scoped tools take.

Processing runs in the background, so call this tool again a few seconds later to see project_id appear (or the state change).

The proposal answer

create_proposal, update_proposal and list_my_proposals answer with the same proposal object:

Field Description
id Id of the proposal, to pass as proposal_id to update_proposal.
state incomplete, waiting_validation or refused — see below.
missing_fields Present only when state is incomplete: the fields to fill in with update_proposal. A funding target is reported as goal (nb_products_min or goal_range also satisfy it) and rewards as rewards_type (rewards also satisfies it).
project_id Id of the project created from the proposal, once it has been processed — a few seconds after it is complete, without waiting for the Ulule team’s acceptance. Absent until then.
type project, presale or membership.
name, description, country, currency, lang, goal, rewards_type The values recorded so far.

The state tells the client what to do next, without exposing Ulule’s internal review steps:

state Meaning What to do
incomplete A draft: something required is still missing. It is not reviewed yet. Fill in every field listed in missing_fields with update_proposal.
waiting_validation Complete and under review by the Ulule team, who will accept or refuse it. The proposal is processed automatically a few seconds after it is complete. As soon as project_id is present, fill in the project with the project-scoped tools — do not wait for the team’s acceptance. project_id usually appears within seconds, but may take a few minutes.
refused Ulule turned the proposal, or the project it became, down. Tell the user; there is nothing more to do.

update_project

Rewrite the title or description of one of the user’s projects. Only the fields you pass are changed, and neither can be emptied.

This tool cannot change the image, the funding goal, the dates, the rewards or the status. For those, the answer returns backoffice links to send the user to.

Parameter Required Description
project_id yes Id of the project (a slug is not accepted).
name Project title, keyed by language code, e.g. {"fr": "Le Semainier"}.
description Project description (HTML allowed), keyed by language code.

At least one of name or description must be provided.

update_goal

Set the type and the funding goal of one of the user’s projects. Only the fields you pass are changed.

Changing the type is only possible while the project is still a draft, and the goal can no longer be changed once the campaign is finishing. When the project’s state forbids the change, the tool answers with the reason.

Parameter Required Description
project_id yes Id of the project (a slug is not accepted).
type presale (pre-order campaign) or project (all-or-nothing fundraising).
goal Funding target as a whole amount in the main unit of the project’s currency, e.g. 5000 for 5000.00 EUR — not cents. For a presale, it is the number of pre-orders to reach instead. Must be positive.

At least one of type or goal must be provided.

The answer also returns the project’s currency and goal_type next to goal: when goal_type is financial, goal is money in the main unit of that currency; for any other value, it is a count.

create_project_image

Add an image to one of the user’s projects. The bytes are given either base64-encoded (image_base64) or as a public http(s) URL (image_url) — exactly one of the two. Accepted formats are png, jpeg, gif and webp, up to 5.5 MB. A fetched URL must be publicly reachable and is not followed through redirects.

The type chooses the role of the image:

  • main — the campaign’s headline image, shown at the top of the public project page (at least 640×360). One per language.
  • background — the page background image (at least 1440×530). One per language.
  • secondary — an extra image kept in the project’s media library. It is stored but not displayed on the public page on its own; to show it, take the url this tool returns and embed it in the project description with update_project.

main and background can each be set once per language and cannot be replaced here — do that from the back office. This tool never changes the goal, dates, rewards or status.

Parameter Required Description
project_id yes Id of the project (a slug is not accepted).
type yes One of main, background, secondary.
lang yes Language code the image is set for, e.g. fr.
image_base64 The image bytes, base64-encoded. A data: URI prefix is accepted. Give this or image_url, not both.
image_url Public http(s) URL to fetch the image from. Give this or image_base64, not both.

The answer returns the stored image’s id, type, lang and (except for a background) its url.

create_reward

Add a reward (contrepartie) to one of the user’s projects: its title, description, price and, optionally, an image.

An image is optional. When given, it is provided either base64-encoded (image_base64) or as a public http(s) URL (image_url) — at most one of the two — and becomes the reward’s picture. Any size is accepted (png, jpeg, gif or webp, up to 5.5 MB); unlike a project’s main or background image, a reward image has no minimum dimensions.

This tool cannot set the stock, variants, options, shipping or tax settings. For those, the answer returns a backoffice_url to send the user to.

Parameter Required Description
project_id yes Id of the project (a slug is not accepted).
title yes Reward title, keyed by language code, e.g. {"fr": "Un tote bag"}. Must include the project’s main language.
price yes Price a backer pays for the reward, in the main unit of the project’s currency, e.g. 25 for 25.00 — not cents. Must be at least 1.
description Reward description (HTML allowed), keyed by language code.
image_base64 Reward image, base64-encoded. A data: URI prefix is accepted. Give this or image_url, not both.
image_url Public http(s) URL to fetch the reward image from. Give this or image_base64, not both.

create_news

Write a news update on one of the user’s projects. The news is saved as a draft and nothing is sent — no e-mail goes out until the user publishes it themselves from the project’s back office. This tool cannot publish, schedule, or attach an image or a video.

Parameter Required Description
project_id yes Id of the project to publish the news on.
title yes News title, keyed by language code. The project’s own language is required.
content yes News body (HTML allowed), keyed by language code. The project’s own language is required.
audience_type Who the news is for: all, supporters, fans, paying-members or tip-supporters. Defaults to all.

Changelog

Notable changes to the public Ulule MCP server and its documentation.

The server is stateless and rolls out backward-compatible changes — new tools, new optional arguments — without versioning. Any breaking change will be announced here first.

2026-10-22

2026-10-15

  • update_goal now also returns currency and goal_type next to goal. A financial goal is an amount in the main unit of the currency; any other goal type is a count. See update_goal.
  • Monetary inputs (goal of update_goal, price of create_reward) are documented as numbers in the currency’s main unit, not cents.
  • All tools now declare a title and the MCP behaviour hints (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) in tools/list. See Tool annotations.
  • The create_news description no longer carries instructions for the assistant; the response still contains the draft’s URL.

2026-10-12

  • Proposal tools now say a complete proposal is processed automatically, a few seconds after it is complete: project_id then appears and the project can be filled in without waiting for the Ulule team to accept the proposal. The waiting_validation state value is unchanged. See The proposal answer.
  • update_proposal on a processed proposal now fails with has already been processed and cannot be updated: edit its project instead.

2026-10-07

  • New For AI assistants section: the step-by-step path from a proposal to a project, for an assistant acting on a user’s behalf.

2026-10-06

  • Breaking: create_proposal, update_proposal and list_my_proposals no longer return status and submitted. They return a state — incomplete, waiting_validation or refused — and, for an incomplete proposal, the missing_fields still to fill in. See The proposal answer.
  • An incomplete proposal is no longer reviewed before it is complete.

2026-09-16

  • New tools: update_goal (set a project’s funding goal), create_project_image (add an image to a project) and create_reward (add a reward). See Tools.

2026-09-15

  • Public MCP server documented at https://api.ulule.com/mcp/public, as a surface distinct from the Ulule REST API.
  • OAuth 2.1 connection flow, endpoints, token usage and rate limiting. See Connecting.
  • Initial tool set: ping, create_proposal, update_proposal, list_my_proposals, update_project and create_news. See Tools.