Ulule MCP

Création de votre projet de financement participatif

Hosted MCP Server

npx add-mcp 'https://api.ulule.com/mcp/public'

Installs into Claude Code, Codex, Cursor and more

Documentation

Ulule MCP

Ulule exposes a public MCP (Model Context Protocol) server. It lets an MCP client — such as Claude, Cursor 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.

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

StepEndpoint
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 tokenPOST https://api.ulule.com/oauth2/token/
Revoke a tokenPOST 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.

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 and rewards; the answer’s submitted field is false while it is still an incomplete draft and true once complete. A complete proposal is processed automatically and is not reviewed by a human right away. If it is accepted, a project is created from it as an unpublished draft: the owner can already start filling it in and get in touch with a coach, who moderates the project before it can go live. That project’s id then appears as project_id in list_my_proposals — the handle for the project-scoped tools.

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

ParameterRequiredDescription
countryyesISO 3166-1 alpha-2 country code, e.g. FR.
descriptionyesWhat the project is — what a coach reads first.
nameProject title.
typeOne of project, presale, membership. Defaults to project.
currencyISO 4217 currency code, e.g. EUR.
langProject language, e.g. fr.
goalFunding target in the chosen currency (for type=project).
goal_rangeFunding target as a {min, max} bracket when no exact figure is set.
nb_products_minMinimum number of units to sell. Required when type=presale.
rewardsWhat backers get, in prose.
rewards_typeconcrete, symbolic, financial, none or undefined.
cityCity where the project is based.
community_rangeAudience the submitter already reaches, as a {min, max} bracket.
date_start_estimationWhen the submitter plans to launch.
legal_entity_typeLegal form of the project holder.
structureCompany or association behind the project.
linksURLs of the project or the submitter’s existing audience.
phone_numberContact phone number (15 characters at most).
referencesAnything 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 nothing is missing, the proposal becomes complete and is processed automatically — there is no separate submit step, and it is not reviewed by a human right away. A proposal already accepted can no longer be edited: it has become a project, whose id shows up as project_id in list_my_proposals. Once accepted, the owner can start filling that project in and get in touch with a coach.

ParameterRequiredDescription
proposal_idyesId of the proposal to update, as returned by create_proposal or list_my_proposals.
othersSame 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 carries the id needed to update it, whether it is complete and has been processed (submitted; a not-yet-submitted proposal is still an incomplete draft), and — once a proposal is accepted and turned into a project — the project_id that the project-scoped tools take.

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.

ParameterRequiredDescription
project_idyesId of the project (a slug is not accepted).
nameProject title, keyed by language code, e.g. {"fr": "Le Semainier"}.
descriptionProject 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.

ParameterRequiredDescription
project_idyesId of the project (a slug is not accepted).
typepresale (pre-order campaign) or project (all-or-nothing fundraising).
goalFunding target as a whole amount in the project’s currency, e.g. 5000. Must be positive.

At least one of type or goal must be provided.

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 and gif, 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.

ParameterRequiredDescription
project_idyesId of the project (a slug is not accepted).
typeyesOne of main, background, secondary.
langyesLanguage code the image is set for, e.g. fr.
image_base64The image bytes, base64-encoded. A data: URI prefix is accepted. Give this or image_url, not both.
image_urlPublic 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 or gif, 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.

ParameterRequiredDescription
project_idyesId of the project (a slug is not accepted).
titleyesReward title, keyed by language code, e.g. {"fr": "Un tote bag"}. Must include the project’s main language.
priceyesPrice a backer pays for the reward, in the project’s currency, e.g. 25 for 25.00. Must be at least 1.
descriptionReward description (HTML allowed), keyed by language code.
image_base64Reward image, base64-encoded. A data: URI prefix is accepted. Give this or image_url, not both.
image_urlPublic 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.

ParameterRequiredDescription
project_idyesId of the project to publish the news on.
titleyesNews title, keyed by language code. The project’s own language is required.
contentyesNews body (HTML allowed), keyed by language code. The project’s own language is required.
audience_typeWho the news is for: all, supporters, fans, paying-members or tip-supporters. Defaults to all.