list_sites()The entry point — every other tool needs a slug from here.
developer tools
pepita is a website publishing tool. Alongside the editor it gives you an MCP server, so you can create, edit, preview and publish a site without leaving your coding agent.
Setup · once
The connector signs in with your existing pepita account. Authorize a client once and it stays authorized until you revoke it in the editor under your avatar → Settings → Devices.
It speaks plain MCP, so any client that reads the
protocol can use it. Also listed in the MCP Registry as
dev.pepita.mcp/mcp, if your client browses the registry.
Settings → Connectors → Add → Browse connectors
→ Search for Pepita
$ claude mcp add --transport http pepita https://mcp.pepita.dev/mcp
$ codex mcp add pepita --url https://mcp.pepita.dev/mcp
$ codex mcp login pepita
# opencode.json
{
"mcp": {
"pepita": {
"type": "remote",
"url": "https://mcp.pepita.dev/mcp",
"enabled": true
}
}
}
$ opencode mcp auth pepita
Reference · every tool
One row per tool, grouped by what it touches. Names and parameters are exactly what the connector answers with, so this doubles as the reference your client sees.
list_sites()The entry point — every other tool needs a slug from here.
create_site(name, allow_embedding?, block_ai_crawlers?)A new site, live the moment it is made, with starter content in it. Shape it afterwards
with write_site_file.
get_site_status(slug)One site at a glance — unpublished changes, live URL, active preview links.
list_site_files(slug, state?, preview?)The file tree: the working copy by default, or the live site, or one preview.
read_site_file(slug, path, state?, preview?)
One file's contents, plus a content sha so a later write can catch conflicts.
write_site_file(slug, path, content)Create a file or replace one whole. Writes to the working copy — nothing is public until you publish.
edit_site_file(slug, path, old_str, new_str)Change part of a file: replace one exact, unique snippet instead of resending the
whole thing. Prefer it over write_site_file on a page that already exists — a
full rewrite costs the entire file, and on a long one it can be cut off half-written.
delete_site_file(slug, path)Remove a file from the working copy. Without it a file written under a mistyped name could only ever be overwritten, never taken away.
read_served_page(slug, path, state?)What a VISITOR gets for one address — the response, not the file. On a pepita site the two differ enormously: a content tag is one line in the file and every item in the response, and an item's own address is in no file at all. Reads the unpublished working copy by default.
publish_site(slug)Live at <slug>.pepita.page, and on any custom domain.
create_preview(slug)A stable URL you can send to anyone, for review before going live.
update_preview(slug, name)Same URL, new content — so a link you already sent shows your latest changes.
list_previews(slug)Their names and URLs — the name is what the update and delete tools take.
delete_preview(slug, name)The version itself is kept — restorable later from the editor's History.
get_form_records_count(slug)A map of form name → how many submissions it holds. The cheap first look, before reading any of them.
list_form_records(slug, form_name, live?, preview?, confirm_large?)One form's submissions, newest first.
live or preview picks the source; neither means your
own test submissions from the editor. Past 100 it answers with the count instead, so your agent can ask
before filling the screen — then call again with confirm_large.
get_form_export_url(slug, form_name, format?, live?, preview?)A time-limited download link — xlsx,
csv or json — so your agent can hand you a file
instead of reading a thousand rows out loud.
list_email_templates(slug)One per form, each matched to the form by name.
read_email_template(slug, form_name)Subject, sender, HTML body, and the sha you need to change it.
write_email_template(slug, form_name, html?, subject?, from?, from_name?)
Creates the template when the form has none and updates it when it does. Leave out
html and pepita seeds a starter body.
Updating an existing template changes the working copy — save it to change the emails
people receive. A brand-new template is saved as it is created.
save_email_template(slug, form_name)The same act as the editor's Save button on the template's row. Editing a template anywhere changes only the working copy; this is what makes those edits the email a visitor actually gets.
delete_email_template(slug, form_name)Confirmation emails for that form stop going out immediately.
list_email_template_images(slug, form_name)The names to write in the body — as bare filenames, not as the URL beside them. pepita expands a filename at send time onto whichever of your domains matches the address the mail goes out from; a URL written in by hand is left exactly as it is, forever.
delete_email_template_image(slug, form_name, name)Emails sent from now on show a gap where it was. Mail already delivered may keep showing it for a long time — a picture in someone's inbox is a copy, and deleting it here cannot reach that.
Repeating content — a blog, a menu, a team page — as a template describing one
item plus the items filling it. The name ties them together, and a page shows the collection with a
<pepita-content> tag you write like any other markup. A collection can have
several layouts: every template command below takes an optional layout name, and leaving it off means
the default one — which is the template itself.
list_content_templates(slug)The starting point — the names here are the names a page asks for.
read_content_template(slug, name, layout?)The complete HTML document for one item — markup only. A <style> or <script> inside it is dropped; those live in the collection's own two files.
write_content_template(slug, name, html, layout?)A brand-new template is saved as it is created; changing one that exists writes a working copy.
save_content_template(slug, name, layout?)A collection's parts are edited together, so one save promotes them together. Nothing pending is a normal outcome, not a failure; a partial save reports what did not land.
delete_content_template(slug, name, layout?)With a layout it removes only that shape; without one, the template and every layout with it.
read_content_file(slug, name, file)A collection's own style.css or script.js. Read before you write: writing replaces the file outright.
write_content_file(slug, name, file, text)Where a collection's look and behaviour live — one of each per collection, applying to every layout. A <style> or <script> written inside a template is dropped silently, so this is the only place either belongs. Changes the working copy; save_content_template with no layout promotes it.
list_content_records(slug, name, oldest?)Newest first, each with its id, its address and whether it is live or still a draft.
add_content_records(slug, name, records)All of them land or none do — a refusal names every bad item by its position.
update_content_record(slug, name, id, data)The published state is unchanged — editing never publishes or unpublishes.
delete_content_record(slug, name, id)One item. The collection and its template are untouched.
publish_content_record(slug, name, id)A new item is a DRAFT — it shows in the editor and on preview links until this is called.
unpublish_content_record(slug, name, id)Nothing is deleted — the item stays in the editor.
add_content_image(slug, name, url)Fetches a picture from a public address into the collection and hands back the pepita url to put in the item.
get_content_image_upload_url(slug, name)A short-lived address that takes one file with no other credential, for an agent that can run a command.
list_videos(slug)Each video with its streaming (HLS) URL and poster. Videos live outside the file tree, so this is how you find them.
get_video_original_url(slug, id)A temporary link to the original file you sent, before streaming prep.
rename_video(slug, id, new_name)Its URLs never change, so pages that reference it keep working.
delete_video(slug, id)Removes the video for good, with its streaming files and poster.
get_building_guide(topic)The exact contract for overview,
forms, confirmation-emails,
video or headers-and-csp. The only tool that takes no
slug, and the one to call before writing any of those — a guessed form action produces a page that looks
right and silently misses the feature.
sign in and out()The connector signs itself in over OAuth the first time you use it — there is no key to paste. Every authorized client is listed in the editor under your avatar → Settings → Devices.
List my pepita sites and show me the files in <slug>.
On <slug>, rewrite the About section to be warmer.
Add a contact form to <slug> and write the confirmation email it sends back.
Give me a shareable preview link for <slug> to send my client.
Publish <slug> to the live site.
Forms · built in
A pepita site collects form submissions with no backend to build and no third-party form service. Point a plain HTML form at pepita and every submission is stored, readable from the connector or from the editor's Forms panel. Add an email template and pepita also emails the person back.
Two things make it work: the action, and a hidden
_form field naming the form. Every other named input becomes a field of the submission,
and its name becomes a column in the editor’s Forms panel.
<form method="post" action="/__pepita/forms">
<input type="hidden" name="_form" value="contact">
<input type="email" name="email" required>
<textarea name="message"></textarea>
<button>Send</button>
</form>
Write the real action and nothing around it — pepita injects its own spam
check and delivery token into any form pointed there, so never add hidden fields of your own beginning with
_. Optionally,
<input type="hidden" name="_redirect" value="/thanks.html"> sends the visitor to
your own page afterwards instead of pepita's built-in one.
A form emails the person who filled it out only if it has a template — an HTML
email body plus a subject and a sender, matched to the form by name. Write one with
write_email_template, or by hand in the editor's Forms panel, in that form's own block. {{field}} placeholders in the subject and the
body fill from the submission, so Thanks {{name}}! works. Deleting the template turns
the email back off.
The name is the whole link between the two, so it is worth seeing twice. The form above
declares _form="contact"; the template that answers it is called
contact as well, and that is the entire wiring — there is no id to copy and nothing
to connect in a settings screen.
# the form said: name="_form" value="contact"
write_email_template(slug, form_name="contact", subject="Thanks, {{name}}!",
from="hello", from_name="Acme", html=…)
save_email_template(slug, form_name="contact")
save_email_template, or the Save button on the template's row in the editor, where a
dot marks anything unsaved. Creating a brand-new template is the one exception: it is saved as it is created,
because there is no earlier version for it to be pending against.email.
Pepita reads the address
from the submission's email field, every time. So a form that should send a
confirmation must carry an input named exactly email (a visible one, or a hidden input
your script fills). A submission without it has nobody to write to, and the email simply doesn't go out. The
editor's Forms panel flags both cases on the form itself.A template carries its own pictures. Drop a JPEG or PNG onto that form's block in the editor's Forms panel.
Reference one by its
bare filename — <img src="hero.jpg"> — and pepita expands it
to a full URL as the email goes out, on whichever of your domains matches the address it is sent from. That
is also why adding a custom domain later doesn't break every picture in the template: the body never held a
hostname to go stale.
https:// address. A relative one resolves against nothing in an inbox —
there is no page for it to be relative to — so it is broken there even when a preview looks right. The bare
filename above is the exception, and only because pepita expands it for you before sending.JPEG and PNG only, decided by the file's contents rather than its name. SVG is refused because mail clients block it, and WebP because support is still patchy exactly where it would matter. Up to 300 KB a picture, and 1 MB or ten pictures a template — an email is read on a phone, often on a slow connection.
Submissions are view-only — pepita stores and shows them, and the confirmation email is the one
thing it sends on your behalf. For the full contract, including what the Forms panel's source filter does with test
submissions, ask the connector for get_building_guide with topic
forms or confirmation-emails.
Images · automatic
When you publish, pepita transcodes every jpg,
jpeg and png into a smaller WebP beside it:
hero.jpg gets a hero.jpg.webp. Your original is never touched
— not moved, not re-encoded, not replaced. There is nothing to configure. The only thing to know is which of the
two URLs to point at.
hero.jpgThe original, byte for byte, forever. Point anything that leaves your site at this one.original
hero.jpg.webpThe optimised copy — often a fraction of the size. Point your site's own markup at this one.optimised
<!-- your own pages: use the optimised copy -->
<img src="/hero.jpg.webp" alt="…">
<style> .hero { background-image: url(/hero.jpg.webp); } </style>
<!-- anything that leaves the site: use the ORIGINAL -->
<meta property="og:image" content="https://you.pepita.page/hero.jpg">
.webp cannot break your site. If
the optimised copy doesn't exist — a brand-new image, one that didn't come out any smaller, or a site you
haven't published since adding it — the server hands back
the original's bytes, with the original's content type. So it is safe to write the
.webp path before it exists. The worst case is a missed optimisation, never a broken
image.og:image), email, and any URL someone might paste elsewhere should point at
hero.jpg. WebP support in link-preview scrapers and mail clients is still patchy — and
a missing social card is the kind of thing you only discover after posting. The original's URL is a permanent
public contract: it will always serve exactly the file you uploaded.jpg · jpeg · pngThe only formats transcoded. svg, gif, ico and files that are already webp are served untouched.
only if smallerSome images — flat-colour PNGs especially — come out bigger as WebP. Those get no copy at all, and the fallback quietly serves the original.
2560px long edgeAnything bigger is scaled down to fit, aspect ratio preserved, never scaled up. This, not the format change, is where most of the saving comes from.
your file winsUpload your own hero.jpg.webp and pepita leaves it exactly as you made it. It never writes over a file you put there.
hero.jpg and ignore the copy.
Nothing obliges you to use it, for one image or across the whole site. (If pepita's AI is writing your pages,
tell it so and it will do the same.)