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, 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

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.

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 on behalf of the user. A proposal is the dossier Ulule reviews before a project may be published — it is not the project itself, and a coach moderates whatever comes out of it.

Only country and description are required, so a proposal can be started now and completed later with update_proposal. It reaches the coaches once it has enough information (country, currency, lang, description, a funding target and rewards); the answer’s submitted field tells you whether it is there yet. Once Ulule validates a proposal, a project is created and its id appears as project_id in list_my_proposals — that id is 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.

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 nothing is missing, the proposal is submitted to the coaches automatically — there is no separate submit step. A proposal already accepted by Ulule can no longer be edited: it has become a project.

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 carries the id needed to update it, whether it has been submitted to the coaches, and — once validated 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.

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.

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.